Remove experimental Linked Identities feature
This commit is contained in:
@@ -20,11 +20,8 @@ package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -34,41 +31,30 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.linked.UriAttribute;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter.ViewHolder;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.AutocryptPeerInfo;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.IdentityInfo;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.LinkedIdInfo;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.UserIdInfo;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||
import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker;
|
||||
|
||||
|
||||
public class IdentityAdapter extends RecyclerView.Adapter<ViewHolder> {
|
||||
private static final int VIEW_TYPE_USER_ID = 0;
|
||||
private static final int VIEW_TYPE_LINKED_ID = 1;
|
||||
|
||||
|
||||
private final Context context;
|
||||
private final LayoutInflater layoutInflater;
|
||||
private final IdentityClickListener identityClickListener;
|
||||
|
||||
private List<IdentityInfo> data;
|
||||
private boolean isSecret;
|
||||
|
||||
|
||||
public IdentityAdapter(Context context, IdentityClickListener identityClickListener) {
|
||||
super();
|
||||
this.layoutInflater = LayoutInflater.from(context);
|
||||
this.context = context;
|
||||
this.identityClickListener = identityClickListener;
|
||||
}
|
||||
|
||||
public void setData(List<IdentityInfo> data, boolean isSecret) {
|
||||
public void setData(List<IdentityInfo> data) {
|
||||
this.data = data;
|
||||
this.isSecret = isSecret;
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@@ -83,8 +69,6 @@ public class IdentityAdapter extends RecyclerView.Adapter<ViewHolder> {
|
||||
} else {
|
||||
((UserIdViewHolder) holder).bind((UserIdInfo) info);
|
||||
}
|
||||
} else if (viewType == VIEW_TYPE_LINKED_ID) {
|
||||
((LinkedIdViewHolder) holder).bind(context, (LinkedIdInfo) info, isSecret);
|
||||
} else {
|
||||
throw new IllegalStateException("unhandled identitytype!");
|
||||
}
|
||||
@@ -96,9 +80,6 @@ public class IdentityAdapter extends RecyclerView.Adapter<ViewHolder> {
|
||||
if (viewType == VIEW_TYPE_USER_ID) {
|
||||
return new UserIdViewHolder(
|
||||
layoutInflater.inflate(R.layout.view_key_identity_user_id, parent, false), identityClickListener);
|
||||
} else if (viewType == VIEW_TYPE_LINKED_ID) {
|
||||
return new LinkedIdViewHolder(layoutInflater.inflate(R.layout.linked_id_item, parent, false),
|
||||
identityClickListener);
|
||||
} else {
|
||||
throw new IllegalStateException("unhandled identitytype!");
|
||||
}
|
||||
@@ -109,8 +90,6 @@ public class IdentityAdapter extends RecyclerView.Adapter<ViewHolder> {
|
||||
IdentityInfo info = data.get(position);
|
||||
if (info instanceof UserIdInfo || info instanceof AutocryptPeerInfo) {
|
||||
return VIEW_TYPE_USER_ID;
|
||||
} else if (info instanceof LinkedIdInfo) {
|
||||
return VIEW_TYPE_LINKED_ID;
|
||||
} else {
|
||||
throw new IllegalStateException("unhandled identitytype!");
|
||||
}
|
||||
@@ -131,69 +110,6 @@ public class IdentityAdapter extends RecyclerView.Adapter<ViewHolder> {
|
||||
}
|
||||
}
|
||||
|
||||
public static class LinkedIdViewHolder extends ViewHolder {
|
||||
public final ImageView vVerified;
|
||||
final private ImageView vIcon;
|
||||
final private TextView vTitle;
|
||||
final private TextView vComment;
|
||||
|
||||
public LinkedIdViewHolder(View view, final IdentityClickListener identityClickListener) {
|
||||
super(view);
|
||||
|
||||
vVerified = view.findViewById(R.id.linked_id_certified_icon);
|
||||
vIcon = view.findViewById(R.id.linked_id_type_icon);
|
||||
vTitle = view.findViewById(R.id.linked_id_title);
|
||||
vComment = view.findViewById(R.id.linked_id_comment);
|
||||
|
||||
view.setOnClickListener(v -> {
|
||||
if (identityClickListener != null) {
|
||||
identityClickListener.onClickIdentity(getAdapterPosition());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void bind(Context context, LinkedIdInfo info, boolean isSecret) {
|
||||
bindVerified(context, info, isSecret);
|
||||
|
||||
UriAttribute uriAttribute = info.getLinkedAttribute();
|
||||
bind(context, uriAttribute);
|
||||
}
|
||||
|
||||
public void bind(Context context, UriAttribute uriAttribute) {
|
||||
vTitle.setText(uriAttribute.getDisplayTitle(context));
|
||||
|
||||
String comment = uriAttribute.getDisplayComment(context);
|
||||
if (comment != null) {
|
||||
vComment.setVisibility(View.VISIBLE);
|
||||
vComment.setText(comment);
|
||||
} else {
|
||||
vComment.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
vIcon.setImageResource(uriAttribute.getDisplayIcon());
|
||||
}
|
||||
|
||||
private void bindVerified(Context context, IdentityInfo info, boolean isSecret) {
|
||||
if (!isSecret) {
|
||||
if (info.isVerified()) {
|
||||
KeyFormattingUtils.setStatusImage(context, vVerified,
|
||||
null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
|
||||
} else {
|
||||
KeyFormattingUtils.setStatusImage(context, vVerified,
|
||||
null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void seekAttention() {
|
||||
if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
|
||||
ObjectAnimator anim = SubtleAttentionSeeker.tintText(vComment, 1000);
|
||||
anim.setStartDelay(200);
|
||||
anim.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class UserIdViewHolder extends ViewHolder {
|
||||
private final TextView vName;
|
||||
private final TextView vAddress;
|
||||
|
||||
@@ -8,10 +8,10 @@ import android.arch.lifecycle.Transformations;
|
||||
import android.arch.lifecycle.ViewModel;
|
||||
import android.content.Context;
|
||||
|
||||
import org.sufficientlysecure.keychain.daos.KeyMetadataDao;
|
||||
import org.sufficientlysecure.keychain.livedata.GenericLiveData;
|
||||
import org.sufficientlysecure.keychain.model.KeyMetadata;
|
||||
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
|
||||
import org.sufficientlysecure.keychain.daos.KeyMetadataDao;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.IdentityInfo;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao;
|
||||
@@ -26,13 +26,12 @@ public class KeyFragmentViewModel extends ViewModel {
|
||||
private LiveData<SystemContactInfo> systemContactInfo;
|
||||
private LiveData<KeyMetadata> keyserverStatus;
|
||||
|
||||
LiveData<List<IdentityInfo>> getIdentityInfo(Context context, LiveData<UnifiedKeyInfo> unifiedKeyInfoLiveData,
|
||||
boolean showLinkedIds) {
|
||||
LiveData<List<IdentityInfo>> getIdentityInfo(Context context, LiveData<UnifiedKeyInfo> unifiedKeyInfoLiveData) {
|
||||
if (identityInfo == null) {
|
||||
IdentityDao identityDao = IdentityDao.getInstance(context);
|
||||
identityInfo = Transformations.switchMap(unifiedKeyInfoLiveData,
|
||||
(unifiedKeyInfo) -> unifiedKeyInfo == null ? null : new GenericLiveData<>(context,
|
||||
() -> identityDao.getIdentityInfos(unifiedKeyInfo.master_key_id(), showLinkedIds)));
|
||||
() -> identityDao.getIdentityInfos(unifiedKeyInfo.master_key_id())));
|
||||
}
|
||||
return identityInfo;
|
||||
}
|
||||
|
||||
@@ -1,529 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.keyview;
|
||||
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import android.arch.lifecycle.LiveData;
|
||||
import android.arch.lifecycle.ViewModel;
|
||||
import android.arch.lifecycle.ViewModelProviders;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextSwitcher;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ViewAnimator;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants.key;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedResource;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||
import org.sufficientlysecure.keychain.linked.UriAttribute;
|
||||
import org.sufficientlysecure.keychain.daos.CertificationDao;
|
||||
import org.sufficientlysecure.keychain.livedata.GenericLiveData;
|
||||
import org.sufficientlysecure.keychain.model.Certification.CertDetails;
|
||||
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
|
||||
import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.daos.KeyRepository;
|
||||
import org.sufficientlysecure.keychain.daos.KeyRepository.NotFoundException;
|
||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
|
||||
import org.sufficientlysecure.keychain.service.CertifyActionsParcel.CertifyAction;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.LinkedIdViewFragment.ViewHolder.VerifyState;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.LinkedIdInfo;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker;
|
||||
import org.sufficientlysecure.keychain.ui.widget.CertListWidget;
|
||||
import org.sufficientlysecure.keychain.ui.widget.KeySpinner;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
public class LinkedIdViewFragment extends CryptoOperationFragment implements OnBackStackChangedListener {
|
||||
|
||||
private static final String ARG_LID_RANK = "rank";
|
||||
private static final String ARG_IS_SECRET = "verified";
|
||||
private static final String ARG_MASTER_KEY_ID = "master_key_id";
|
||||
|
||||
private long masterKeyId;
|
||||
private boolean isSecret;
|
||||
|
||||
private UriAttribute linkedId;
|
||||
private LinkedTokenResource linkedResource;
|
||||
|
||||
private AsyncTask taskInProgress;
|
||||
|
||||
private ViewHolder viewHolder;
|
||||
private int lidRank;
|
||||
private long certifyKeyId;
|
||||
|
||||
public static LinkedIdViewFragment newInstance(long masterKeyId, int rank, boolean isSecret) {
|
||||
LinkedIdViewFragment frag = new LinkedIdViewFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(ARG_LID_RANK, rank);
|
||||
args.putBoolean(ARG_IS_SECRET, isSecret);
|
||||
args.putLong(ARG_MASTER_KEY_ID, masterKeyId);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
public LinkedIdViewFragment() {
|
||||
// IMPORTANT: the id must be unique in the ViewKeyActivity CryptoOperationHelper id namespace!
|
||||
// no initial progress message -> we handle progress ourselves!
|
||||
super(5, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = getArguments();
|
||||
lidRank = args.getInt(ARG_LID_RANK);
|
||||
|
||||
isSecret = args.getBoolean(ARG_IS_SECRET);
|
||||
masterKeyId = args.getLong(ARG_MASTER_KEY_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
LinkedIdViewModel viewModel = ViewModelProviders.of(this).get(LinkedIdViewModel.class);
|
||||
viewModel.getLinkedIdInfo(requireContext(), masterKeyId, lidRank).observe(this, this::onLinkedIdInfoLoaded);
|
||||
viewModel.getCertifyingKeys(requireContext()).observe(this, viewHolder.vKeySpinner::setData);
|
||||
}
|
||||
|
||||
private void onLinkedIdInfoLoaded(LinkedIdInfo linkedIdInfo) {
|
||||
if (linkedIdInfo == null) {
|
||||
Timber.e("error loading identity");
|
||||
Notify.create(getActivity(), "Error loading linked identity!",
|
||||
Notify.LENGTH_LONG, Style.ERROR).show();
|
||||
finishFragment();
|
||||
return;
|
||||
}
|
||||
|
||||
loadIdentity(linkedIdInfo.getLinkedAttribute(), linkedIdInfo.isVerified());
|
||||
}
|
||||
|
||||
public void finishFragment() {
|
||||
new Handler().post(() -> {
|
||||
FragmentManager manager = getFragmentManager();
|
||||
manager.removeOnBackStackChangedListener(LinkedIdViewFragment.this);
|
||||
manager.popBackStack("linked_id", FragmentManager.POP_BACK_STACK_INCLUSIVE);
|
||||
});
|
||||
}
|
||||
|
||||
private void loadIdentity(LinkedAttribute linkedId, boolean isVerified) {
|
||||
this.linkedId = linkedId;
|
||||
|
||||
LinkedResource res = ((LinkedAttribute) this.linkedId).mResource;
|
||||
linkedResource = (LinkedTokenResource) res;
|
||||
|
||||
if (!isSecret) {
|
||||
if (isVerified) {
|
||||
KeyFormattingUtils.setStatusImage(getContext(), viewHolder.mLinkedIdHolder.vVerified,
|
||||
null, State.VERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
|
||||
} else {
|
||||
KeyFormattingUtils.setStatusImage(getContext(), viewHolder.mLinkedIdHolder.vVerified,
|
||||
null, State.UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
|
||||
}
|
||||
} else {
|
||||
viewHolder.mLinkedIdHolder.vVerified.setImageResource(R.drawable.octo_link_24dp);
|
||||
}
|
||||
|
||||
viewHolder.mLinkedIdHolder.bind(getContext(), this.linkedId);
|
||||
|
||||
setShowVerifying(false);
|
||||
|
||||
if (linkedResource.isViewable()) {
|
||||
viewHolder.vButtonView.setVisibility(View.VISIBLE);
|
||||
viewHolder.vButtonView.setOnClickListener(v -> {
|
||||
Intent intent = linkedResource.getViewIntent();
|
||||
if (intent == null) {
|
||||
return;
|
||||
}
|
||||
startActivity(intent);
|
||||
});
|
||||
} else {
|
||||
viewHolder.vButtonView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ViewHolder {
|
||||
private final View vButtonView;
|
||||
private final ViewAnimator vVerifyingContainer;
|
||||
private final ViewAnimator vItemCertified;
|
||||
private final View vKeySpinnerContainer;
|
||||
IdentityAdapter.LinkedIdViewHolder mLinkedIdHolder;
|
||||
|
||||
private ViewAnimator vButtonSwitcher;
|
||||
private CertListWidget vLinkedCerts;
|
||||
private KeySpinner vKeySpinner;
|
||||
private final View vButtonVerify;
|
||||
private final View vButtonRetry;
|
||||
private final View vButtonConfirm;
|
||||
|
||||
private final ViewAnimator vProgress;
|
||||
private final TextSwitcher vText;
|
||||
|
||||
ViewHolder(View root) {
|
||||
vLinkedCerts = root.findViewById(R.id.linked_id_certs);
|
||||
vKeySpinner = root.findViewById(R.id.cert_key_spinner);
|
||||
vKeySpinnerContainer = root.findViewById(R.id.cert_key_spincontainer);
|
||||
vButtonSwitcher = root.findViewById(R.id.button_animator);
|
||||
|
||||
mLinkedIdHolder = new IdentityAdapter.LinkedIdViewHolder(root, null);
|
||||
|
||||
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 = root.findViewById(R.id.linked_verify_container);
|
||||
vItemCertified = root.findViewById(R.id.linked_id_certified);
|
||||
|
||||
vProgress = root.findViewById(R.id.linked_cert_progress);
|
||||
vText = root.findViewById(R.id.linked_cert_text);
|
||||
|
||||
vKeySpinner.setShowNone(R.string.choice_select_cert);
|
||||
}
|
||||
|
||||
enum VerifyState {
|
||||
VERIFYING, VERIFY_OK, VERIFY_ERROR, CERTIFYING
|
||||
}
|
||||
|
||||
void setVerifyingState(Context context, VerifyState state, boolean isSecret) {
|
||||
switch (state) {
|
||||
case VERIFYING:
|
||||
vProgress.setDisplayedChild(0);
|
||||
vText.setText(context.getString(R.string.linked_text_verifying));
|
||||
vKeySpinnerContainer.setVisibility(View.GONE);
|
||||
break;
|
||||
|
||||
case VERIFY_OK:
|
||||
vProgress.setDisplayedChild(1);
|
||||
if (!isSecret) {
|
||||
showButton(2);
|
||||
if (!vKeySpinner.isSingleEntry()) {
|
||||
vKeySpinnerContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
showButton(1);
|
||||
vKeySpinnerContainer.setVisibility(View.GONE);
|
||||
}
|
||||
break;
|
||||
|
||||
case VERIFY_ERROR:
|
||||
showButton(1);
|
||||
vProgress.setDisplayedChild(2);
|
||||
vText.setText(context.getString(R.string.linked_text_error));
|
||||
vKeySpinnerContainer.setVisibility(View.GONE);
|
||||
break;
|
||||
|
||||
case CERTIFYING:
|
||||
vProgress.setDisplayedChild(0);
|
||||
vText.setText(context.getString(R.string.linked_text_confirming));
|
||||
vKeySpinnerContainer.setVisibility(View.GONE);
|
||||
vButtonConfirm.setEnabled(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void showVerifyingContainer(Context context, boolean show, boolean isSecret) {
|
||||
if (vVerifyingContainer.getDisplayedChild() == (show ? 1 : 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
vVerifyingContainer.setInAnimation(context, show ? R.anim.fade_in_up : R.anim.fade_in_down);
|
||||
vVerifyingContainer.setOutAnimation(context, show ? R.anim.fade_out_up : R.anim.fade_out_down);
|
||||
vVerifyingContainer.setDisplayedChild(show ? 1 : 0);
|
||||
|
||||
vItemCertified.setInAnimation(context, show ? R.anim.fade_in_up : R.anim.fade_in_down);
|
||||
vItemCertified.setOutAnimation(context, show ? R.anim.fade_out_up : R.anim.fade_out_down);
|
||||
vItemCertified.setDisplayedChild(show || isSecret ? 1 : 0);
|
||||
}
|
||||
|
||||
void showButton(int which) {
|
||||
if (vButtonSwitcher.getDisplayedChild() == which) {
|
||||
return;
|
||||
}
|
||||
vButtonSwitcher.setDisplayedChild(which);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean mVerificationState = false;
|
||||
/** Switches between the 'verifying' ui bit and certificate status. This method
|
||||
* must behave correctly in all states, showing or hiding the appropriate views
|
||||
* and cancelling pending operations where necessary.
|
||||
*
|
||||
* This method also handles back button functionality in combination with
|
||||
* onBackStateChanged.
|
||||
*/
|
||||
void setShowVerifying(boolean show) {
|
||||
if (!show) {
|
||||
if (taskInProgress != null) {
|
||||
taskInProgress.cancel(false);
|
||||
taskInProgress = null;
|
||||
}
|
||||
getFragmentManager().removeOnBackStackChangedListener(this);
|
||||
new Handler().post(() -> getFragmentManager().popBackStack("verification",
|
||||
FragmentManager.POP_BACK_STACK_INCLUSIVE));
|
||||
|
||||
if (!mVerificationState) {
|
||||
return;
|
||||
}
|
||||
mVerificationState = false;
|
||||
|
||||
viewHolder.showButton(0);
|
||||
viewHolder.vKeySpinnerContainer.setVisibility(View.GONE);
|
||||
viewHolder.showVerifyingContainer(getContext(), false, isSecret);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mVerificationState) {
|
||||
return;
|
||||
}
|
||||
mVerificationState = true;
|
||||
|
||||
FragmentManager manager = getFragmentManager();
|
||||
manager.beginTransaction().addToBackStack("verification").commit();
|
||||
manager.executePendingTransactions();
|
||||
manager.addOnBackStackChangedListener(this);
|
||||
viewHolder.showVerifyingContainer(getContext(), true, isSecret);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
setShowVerifying(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
|
||||
View root = inflater.inflate(R.layout.linked_id_view_fragment, superContainer, false);
|
||||
Context context = getContext();
|
||||
if (context == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
|
||||
viewHolder = new ViewHolder(root);
|
||||
root.setTag(viewHolder);
|
||||
|
||||
((ImageView) root.findViewById(R.id.status_icon_verified))
|
||||
.setColorFilter(ContextCompat.getColor(context, R.color.android_green_light),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
((ImageView) root.findViewById(R.id.status_icon_invalid))
|
||||
.setColorFilter(ContextCompat.getColor(context, R.color.android_red_light),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
|
||||
viewHolder.vButtonVerify.setOnClickListener(v -> verifyResource());
|
||||
viewHolder.vButtonRetry.setOnClickListener(v -> verifyResource());
|
||||
viewHolder.vButtonConfirm.setOnClickListener(v -> initiateCertifying());
|
||||
|
||||
LinkedIdViewModel viewModel = ViewModelProviders.of(this).get(LinkedIdViewModel.class);
|
||||
viewModel.getCertDetails(context, masterKeyId, lidRank).observe(this, this::onLoadCertDetails);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void onLoadCertDetails(CertDetails certDetails) {
|
||||
viewHolder.vLinkedCerts.setData(certDetails, isSecret);
|
||||
}
|
||||
|
||||
void verifyResource() {
|
||||
|
||||
// only one at a time (no sync needed, taskInProgress is only touched in ui thread)
|
||||
if (taskInProgress != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setShowVerifying(true);
|
||||
|
||||
viewHolder.vKeySpinnerContainer.setVisibility(View.GONE);
|
||||
viewHolder.setVerifyingState(getContext(), VerifyState.VERIFYING, isSecret);
|
||||
|
||||
taskInProgress = new AsyncTask<Void,Void,LinkedVerifyResult>() {
|
||||
@Override
|
||||
protected LinkedVerifyResult doInBackground(Void... params) {
|
||||
FragmentActivity activity = getActivity();
|
||||
|
||||
byte[] fingerprint;
|
||||
try {
|
||||
fingerprint = KeyRepository.create(activity).getFingerprintByKeyId(masterKeyId);
|
||||
} catch (NotFoundException e) {
|
||||
throw new IllegalStateException("Key to verify linked id for must exist in db!");
|
||||
}
|
||||
|
||||
long timer = System.currentTimeMillis();
|
||||
LinkedVerifyResult result = linkedResource.verify(activity, fingerprint);
|
||||
|
||||
// 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()) {
|
||||
viewHolder.vText.setText(getString(linkedResource.getVerifiedText(isSecret)));
|
||||
// hack to preserve bold text
|
||||
((TextView) viewHolder.vText.getCurrentView()).setText(
|
||||
linkedResource.getVerifiedText(isSecret));
|
||||
viewHolder.setVerifyingState(getContext(), VerifyState.VERIFY_OK, isSecret);
|
||||
viewHolder.mLinkedIdHolder.seekAttention();
|
||||
} else {
|
||||
viewHolder.setVerifyingState(getContext(), VerifyState.VERIFY_ERROR, isSecret);
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
taskInProgress = null;
|
||||
}
|
||||
}.execute();
|
||||
|
||||
}
|
||||
|
||||
private void initiateCertifying() {
|
||||
|
||||
if (isSecret) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get the user's passphrase for this key (if required)
|
||||
certifyKeyId = viewHolder.vKeySpinner.getSelectedKeyId();
|
||||
if (certifyKeyId == key.none || certifyKeyId == key.symmetric) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
SubtleAttentionSeeker.tintBackground(viewHolder.vKeySpinnerContainer, 600).start();
|
||||
} else {
|
||||
Notify.create(getActivity(), R.string.select_key_to_certify, Style.ERROR).show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
viewHolder.setVerifyingState(getContext(), VerifyState.CERTIFYING, false);
|
||||
cryptoOperation();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationCancelled() {
|
||||
super.onCryptoOperationCancelled();
|
||||
|
||||
// go back to 'verified ok'
|
||||
setShowVerifying(false);
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Parcelable createOperationInput() {
|
||||
CertifyAction action = CertifyAction.createForUserAttributes(masterKeyId,
|
||||
Collections.singletonList(linkedId.toUserAttribute()));
|
||||
|
||||
// fill values for this action
|
||||
CertifyActionsParcel.Builder builder = CertifyActionsParcel.builder(certifyKeyId);
|
||||
builder.addActions(Collections.singletonList(action));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationSuccess(OperationResult result) {
|
||||
result.createNotify(getActivity()).show();
|
||||
// no need to do anything else, we will get a loader refresh!
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationError(OperationResult result) {
|
||||
result.createNotify(getActivity()).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCryptoSetProgress(String msg, int progress, int max) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static class LinkedIdViewModel extends ViewModel {
|
||||
LiveData<List<UnifiedKeyInfo>> certifyingKeysLiveData;
|
||||
LiveData<CertDetails> certDetailsLiveData;
|
||||
LiveData<LinkedIdInfo> linkedIfInfoLiveData;
|
||||
|
||||
LiveData<List<UnifiedKeyInfo>> getCertifyingKeys(Context context) {
|
||||
if (certifyingKeysLiveData == null) {
|
||||
certifyingKeysLiveData = new GenericLiveData<>(context, () -> {
|
||||
KeyRepository keyRepository = KeyRepository.create(context);
|
||||
return keyRepository.getAllUnifiedKeyInfoWithSecret();
|
||||
});
|
||||
}
|
||||
return certifyingKeysLiveData;
|
||||
}
|
||||
|
||||
LiveData<CertDetails> getCertDetails(Context context, long masterKeyId, int lidRank) {
|
||||
if (certDetailsLiveData == null) {
|
||||
CertificationDao certificationDao = CertificationDao.getInstance(context);
|
||||
certDetailsLiveData = new GenericLiveData<>(context, masterKeyId,
|
||||
() -> certificationDao.getVerifyingCertDetails(masterKeyId, lidRank));
|
||||
}
|
||||
return certDetailsLiveData;
|
||||
}
|
||||
|
||||
public LiveData<LinkedIdInfo> getLinkedIdInfo(Context context, long masterKeyId, int lidRank) {
|
||||
if (linkedIfInfoLiveData == null) {
|
||||
IdentityDao identityDao = IdentityDao.getInstance(context);
|
||||
linkedIfInfoLiveData = new GenericLiveData<>(context, masterKeyId,
|
||||
() -> identityDao.getLinkedIdInfo(masterKeyId, lidRank));
|
||||
}
|
||||
return linkedIfInfoLiveData;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -41,17 +41,16 @@ import android.view.ViewGroup;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||
import org.sufficientlysecure.keychain.daos.AutocryptPeerDao;
|
||||
import org.sufficientlysecure.keychain.model.KeyMetadata;
|
||||
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
|
||||
import org.sufficientlysecure.keychain.daos.AutocryptPeerDao;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.IdentityAdapter.IdentityClickListener;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.AutocryptPeerInfo;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.IdentityInfo;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.LinkedIdInfo;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.IdentityDao.UserIdInfo;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.KeyHealthStatus;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.SubkeyStatusDao.KeySubkeyStatus;
|
||||
@@ -62,8 +61,6 @@ import org.sufficientlysecure.keychain.ui.keyview.view.KeyHealthView;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.view.KeyStatusList.KeyDisplayStatus;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.view.KeyserverStatusView;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.view.SystemContactCardView;
|
||||
import org.sufficientlysecure.keychain.ui.linked.LinkedIdWizard;
|
||||
import org.sufficientlysecure.keychain.util.Preferences;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
@@ -128,8 +125,7 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener
|
||||
|
||||
KeyFragmentViewModel model = ViewModelProviders.of(this).get(KeyFragmentViewModel.class);
|
||||
|
||||
boolean showLinkedIds = Preferences.getPreferences(context).getExperimentalEnableLinkedIdentities();
|
||||
model.getIdentityInfo(context, unifiedKeyInfoLiveData, showLinkedIds).observe(this, this::onLoadIdentityInfo);
|
||||
model.getIdentityInfo(context, unifiedKeyInfoLiveData).observe(this, this::onLoadIdentityInfo);
|
||||
model.getKeyserverStatus(context, unifiedKeyInfoLiveData).observe(this, this::onLoadKeyMetadata);
|
||||
model.getSystemContactInfo(context, unifiedKeyInfoLiveData).observe(this, this::onLoadSystemContact);
|
||||
model.getSubkeyStatus(context, unifiedKeyInfoLiveData).observe(this, this::onLoadSubkeyStatus);
|
||||
@@ -238,21 +234,12 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener
|
||||
return;
|
||||
}
|
||||
|
||||
Context context = requireContext();
|
||||
|
||||
this.unifiedKeyInfo = unifiedKeyInfo;
|
||||
|
||||
boolean showLinkedIds = Preferences.getPreferences(context).getExperimentalEnableLinkedIdentities();
|
||||
boolean isSecret = unifiedKeyInfo.has_any_secret();
|
||||
identitiesCardView.setAddLinkedIdButtonVisible(showLinkedIds && isSecret);
|
||||
identitiesCardView.setIdentitiesCardListener((v) -> addLinkedIdentity());
|
||||
}
|
||||
|
||||
private void showIdentityInfo(final int position) {
|
||||
IdentityInfo info = identitiesAdapter.getInfo(position);
|
||||
if (info instanceof LinkedIdInfo) {
|
||||
showLinkedId((LinkedIdInfo) info);
|
||||
} else if (info instanceof UserIdInfo) {
|
||||
if (info instanceof UserIdInfo) {
|
||||
showUserIdInfo((UserIdInfo) info);
|
||||
} else if (info instanceof AutocryptPeerInfo) {
|
||||
Intent autocryptPeerIntent = ((AutocryptPeerInfo) info).getAutocryptPeerIntent();
|
||||
@@ -266,12 +253,6 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener
|
||||
showContextMenu(position, anchor);
|
||||
}
|
||||
|
||||
private void showLinkedId(final LinkedIdInfo info) {
|
||||
LinkedIdViewFragment frag = LinkedIdViewFragment.newInstance(info.getMasterKeyId(), info.getRank(), unifiedKeyInfo.has_any_secret());
|
||||
|
||||
switchToFragment(frag, "linked_id");
|
||||
}
|
||||
|
||||
private void showUserIdInfo(UserIdInfo info) {
|
||||
if (!unifiedKeyInfo.has_any_secret()) {
|
||||
UserIdInfoDialogFragment dialogFragment = UserIdInfoDialogFragment.newInstance(false, info.isVerified());
|
||||
@@ -279,12 +260,6 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener
|
||||
}
|
||||
}
|
||||
|
||||
private void addLinkedIdentity() {
|
||||
Intent intent = new Intent(requireContext(), LinkedIdWizard.class);
|
||||
intent.putExtra(LinkedIdWizard.EXTRA_MASTER_KEY_ID, unifiedKeyInfo.master_key_id());
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
public void onClickForgetIdentity(int position) {
|
||||
AutocryptPeerInfo info = (AutocryptPeerInfo) identitiesAdapter.getInfo(position);
|
||||
if (info == null) {
|
||||
@@ -296,7 +271,7 @@ public class ViewKeyFragment extends Fragment implements OnMenuItemClickListener
|
||||
}
|
||||
|
||||
private void onLoadIdentityInfo(List<IdentityInfo> identityInfos) {
|
||||
identitiesAdapter.setData(identityInfos, unifiedKeyInfo.has_any_secret());
|
||||
identitiesAdapter.setData(identityInfos);
|
||||
}
|
||||
|
||||
private void onLoadSystemContact(SystemContactInfo systemContactInfo) {
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.sufficientlysecure.keychain.ui.keyview.loader;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -35,17 +34,12 @@ import android.support.annotation.Nullable;
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.squareup.sqldelight.SqlDelightQuery;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
|
||||
import org.sufficientlysecure.keychain.linked.UriAttribute;
|
||||
import org.sufficientlysecure.keychain.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.daos.AutocryptPeerDao;
|
||||
import org.sufficientlysecure.keychain.model.AutocryptPeer;
|
||||
import org.sufficientlysecure.keychain.model.UserPacket;
|
||||
import org.sufficientlysecure.keychain.model.UserPacket.UserAttribute;
|
||||
import org.sufficientlysecure.keychain.model.UserPacket.UserId;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||
import org.sufficientlysecure.keychain.daos.AutocryptPeerDao;
|
||||
import org.sufficientlysecure.keychain.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.ui.util.PackageIconGetter;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
public class IdentityDao {
|
||||
@@ -71,12 +65,9 @@ public class IdentityDao {
|
||||
this.autocryptPeerDao = autocryptPeerDao;
|
||||
}
|
||||
|
||||
public List<IdentityInfo> getIdentityInfos(long masterKeyId, boolean showLinkedIds) {
|
||||
public List<IdentityInfo> getIdentityInfos(long masterKeyId) {
|
||||
ArrayList<IdentityInfo> identities = new ArrayList<>();
|
||||
|
||||
if (showLinkedIds) {
|
||||
loadLinkedIds(identities, masterKeyId);
|
||||
}
|
||||
loadUserIds(identities, masterKeyId);
|
||||
correlateOrAddAutocryptPeers(identities, masterKeyId);
|
||||
|
||||
@@ -132,46 +123,6 @@ public class IdentityDao {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void loadLinkedIds(ArrayList<IdentityInfo> identities, long masterKeyId) {
|
||||
SqlDelightQuery query = UserPacket.FACTORY.selectUserAttributesByTypeAndMasterKeyId(
|
||||
(long) WrappedUserAttribute.UAT_URI_ATTRIBUTE, masterKeyId);
|
||||
try (Cursor cursor = db.query(query)) {
|
||||
while (cursor.moveToNext()) {
|
||||
UserAttribute userAttribute = UserPacket.USER_ATTRIBUTE_MAPPER.map(cursor);
|
||||
|
||||
LinkedIdInfo linkedIdInfo = parseLinkedIdInfo(userAttribute);
|
||||
identities.add(linkedIdInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public LinkedIdInfo getLinkedIdInfo(long masterKeyId, int rank) {
|
||||
SqlDelightQuery query = UserPacket.FACTORY.selectSpecificUserAttribute(
|
||||
(long) WrappedUserAttribute.UAT_URI_ATTRIBUTE, masterKeyId, rank);
|
||||
try (Cursor cursor = db.query(query)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
UserAttribute userAttribute = UserPacket.USER_ATTRIBUTE_MAPPER.map(cursor);
|
||||
|
||||
return parseLinkedIdInfo(userAttribute);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private LinkedIdInfo parseLinkedIdInfo(UserAttribute userAttribute) {
|
||||
try {
|
||||
UriAttribute uriAttribute = LinkedAttribute.fromAttributeData(userAttribute.attribute_data());
|
||||
if (uriAttribute instanceof LinkedAttribute) {
|
||||
return LinkedIdInfo.create(userAttribute.master_key_id(), userAttribute.rank(),
|
||||
userAttribute.isVerified(), userAttribute.is_primary(), (LinkedAttribute) uriAttribute);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Timber.e(e, "Failed parsing uri attribute");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void loadUserIds(ArrayList<IdentityInfo> identities, long... masterKeyId) {
|
||||
SqlDelightQuery query = UserPacket.FACTORY.selectUserIdsByMasterKeyId(masterKeyId);
|
||||
try (Cursor cursor = db.query(query)) {
|
||||
@@ -214,20 +165,6 @@ public class IdentityDao {
|
||||
}
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
public abstract static class LinkedIdInfo implements IdentityInfo {
|
||||
public abstract long getMasterKeyId();
|
||||
public abstract int getRank();
|
||||
public abstract boolean isVerified();
|
||||
public abstract boolean isPrimary();
|
||||
|
||||
public abstract LinkedAttribute getLinkedAttribute();
|
||||
|
||||
static LinkedIdInfo create(long masterKeyId, int rank, boolean isVerified, boolean isPrimary, LinkedAttribute linkedAttribute) {
|
||||
return new AutoValue_IdentityDao_LinkedIdInfo(masterKeyId, rank, isVerified, isPrimary, linkedAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
public abstract static class AutocryptPeerInfo implements IdentityInfo {
|
||||
public abstract long getMasterKeyId();
|
||||
|
||||
@@ -36,8 +36,6 @@ import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoratio
|
||||
public class IdentitiesCardView extends CardView {
|
||||
private final RecyclerView vIdentities;
|
||||
|
||||
private final Button linkedIdsAddButton;
|
||||
|
||||
public IdentitiesCardView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
@@ -46,8 +44,6 @@ public class IdentitiesCardView extends CardView {
|
||||
vIdentities = view.findViewById(R.id.view_key_user_ids);
|
||||
vIdentities.setLayoutManager(new LinearLayoutManager(context));
|
||||
vIdentities.addItemDecoration(new DividerItemDecoration(context, DividerItemDecoration.VERTICAL_LIST, false));
|
||||
|
||||
linkedIdsAddButton = view.findViewById(R.id.view_key_card_linked_ids_add);
|
||||
}
|
||||
|
||||
public void setIdentitiesAdapter(IdentityAdapter identityAdapter) {
|
||||
@@ -59,12 +55,4 @@ public class IdentitiesCardView extends CardView {
|
||||
});
|
||||
vIdentities.setAdapter(identityAdapter);
|
||||
}
|
||||
|
||||
public void setIdentitiesCardListener(OnClickListener identitiesCardListener) {
|
||||
linkedIdsAddButton.setOnClickListener(identitiesCardListener);
|
||||
}
|
||||
|
||||
public void setAddLinkedIdButtonVisible(boolean show) {
|
||||
linkedIdsAddButton.setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.arch.lifecycle.ViewModelProviders;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ViewAnimator;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
|
||||
import org.sufficientlysecure.keychain.operations.results.LinkedVerifyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.UnifiedKeyInfoViewModel;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
|
||||
public abstract class LinkedIdCreateFinalFragment extends CryptoOperationFragment {
|
||||
private ImageView mVerifyImage;
|
||||
private TextView mVerifyStatus;
|
||||
private ViewAnimator mVerifyAnimator;
|
||||
|
||||
private long masterKeyId;
|
||||
byte[] fingerprint;
|
||||
|
||||
// This is a resource, set AFTER it has been verified
|
||||
LinkedTokenResource mVerifiedResource = null;
|
||||
private ViewAnimator mVerifyButtonAnimator;
|
||||
|
||||
protected abstract View newView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
UnifiedKeyInfoViewModel viewModel = ViewModelProviders.of(requireActivity()).get(UnifiedKeyInfoViewModel.class);
|
||||
viewModel.getUnifiedKeyInfoLiveData(requireContext()).observe(this, this::onLoadUnifiedKeyInfo);
|
||||
}
|
||||
|
||||
private void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) {
|
||||
this.masterKeyId = unifiedKeyInfo.master_key_id();
|
||||
this.fingerprint = unifiedKeyInfo.fingerprint();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
final View view = newView(inflater, container, savedInstanceState);
|
||||
|
||||
View nextButton = view.findViewById(R.id.next_button);
|
||||
if (nextButton != null) {
|
||||
nextButton.setOnClickListener(v -> cryptoOperation());
|
||||
}
|
||||
|
||||
view.findViewById(R.id.back_button).setOnClickListener(
|
||||
v -> ((LinkedIdWizard) requireActivity()).loadFragment(null, LinkedIdWizard.FRAG_ACTION_TO_LEFT));
|
||||
|
||||
mVerifyAnimator = view.findViewById(R.id.verify_progress);
|
||||
mVerifyImage = view.findViewById(R.id.verify_image);
|
||||
mVerifyStatus = view.findViewById(R.id.verify_status);
|
||||
mVerifyButtonAnimator = view.findViewById(R.id.verify_buttons);
|
||||
|
||||
view.findViewById(R.id.button_verify).setOnClickListener(v -> proofVerify());
|
||||
|
||||
view.findViewById(R.id.button_retry).setOnClickListener(v -> proofVerify());
|
||||
|
||||
setVerifyProgress(false, null);
|
||||
mVerifyStatus.setText(R.string.linked_verify_pending);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
abstract LinkedTokenResource getResource(OperationLog log);
|
||||
|
||||
private void setVerifyProgress(boolean on, Boolean success) {
|
||||
if (success == null) {
|
||||
mVerifyStatus.setText(R.string.linked_verifying);
|
||||
displayButton(on ? 2 : 0);
|
||||
} else if (success) {
|
||||
mVerifyStatus.setText(R.string.linked_verify_success);
|
||||
mVerifyImage.setImageResource(R.drawable.status_signature_verified_cutout_24dp);
|
||||
mVerifyImage.setColorFilter(getResources().getColor(R.color.android_green_dark),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
displayButton(2);
|
||||
} else {
|
||||
mVerifyStatus.setText(R.string.linked_verify_error);
|
||||
mVerifyImage.setImageResource(R.drawable.status_signature_unknown_cutout_24dp);
|
||||
mVerifyImage.setColorFilter(getResources().getColor(R.color.android_red_dark),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
displayButton(1);
|
||||
}
|
||||
mVerifyAnimator.setDisplayedChild(on ? 1 : 0);
|
||||
}
|
||||
|
||||
public void displayButton(int button) {
|
||||
if (mVerifyButtonAnimator.getDisplayedChild() == button) {
|
||||
return;
|
||||
}
|
||||
mVerifyButtonAnimator.setDisplayedChild(button);
|
||||
}
|
||||
|
||||
protected void proofVerify() {
|
||||
setVerifyProgress(true, null);
|
||||
|
||||
new AsyncTask<Void,Void,LinkedVerifyResult>() {
|
||||
|
||||
@Override
|
||||
protected LinkedVerifyResult doInBackground(Void... params) {
|
||||
long timer = System.currentTimeMillis();
|
||||
|
||||
OperationLog log = new OperationLog();
|
||||
LinkedTokenResource resource = getResource(log);
|
||||
if (resource == null) {
|
||||
return new LinkedVerifyResult(LinkedVerifyResult.RESULT_ERROR, log);
|
||||
}
|
||||
|
||||
LinkedVerifyResult result = resource.verify(getActivity(), fingerprint);
|
||||
|
||||
// ux flow: this operation should take at last a second
|
||||
timer = System.currentTimeMillis() -timer;
|
||||
if (timer < 1000) try {
|
||||
Thread.sleep(1000 -timer);
|
||||
} catch (InterruptedException e) {
|
||||
// never mind
|
||||
}
|
||||
|
||||
if (result.success()) {
|
||||
mVerifiedResource = resource;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(LinkedVerifyResult result) {
|
||||
super.onPostExecute(result);
|
||||
if (result.success()) {
|
||||
setVerifyProgress(false, true);
|
||||
} else {
|
||||
setVerifyProgress(false, false);
|
||||
// on error, show error message
|
||||
result.createNotify(getActivity()).show(LinkedIdCreateFinalFragment.this);
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cryptoOperation() {
|
||||
if (mVerifiedResource == null) {
|
||||
Notify.create(getActivity(), R.string.linked_need_verify, Notify.Style.ERROR)
|
||||
.show(LinkedIdCreateFinalFragment.this);
|
||||
return;
|
||||
}
|
||||
|
||||
super.cryptoOperation();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cryptoOperation(CryptoInputParcel cryptoInput) {
|
||||
if (mVerifiedResource == null) {
|
||||
Notify.create(getActivity(), R.string.linked_need_verify, Notify.Style.ERROR)
|
||||
.show(LinkedIdCreateFinalFragment.this);
|
||||
return;
|
||||
}
|
||||
|
||||
super.cryptoOperation(cryptoInput);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Parcelable createOperationInput() {
|
||||
SaveKeyringParcel.Builder builder=
|
||||
SaveKeyringParcel.buildChangeKeyringParcel(masterKeyId, fingerprint);
|
||||
WrappedUserAttribute ua = LinkedAttribute.fromResource(mVerifiedResource).toUserAttribute();
|
||||
builder.addUserAttribute(ua);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationSuccess(OperationResult result) {
|
||||
requireActivity().finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationError(OperationResult result) {
|
||||
result.createNotify(getActivity()).show(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,671 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.linked;
|
||||
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Random;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.arch.lifecycle.ViewModelProviders;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.util.Base64;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ViewAnimator;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.sufficientlysecure.keychain.BuildConfig;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedAttribute;
|
||||
import org.sufficientlysecure.keychain.linked.resources.GithubResource;
|
||||
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
|
||||
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.UnifiedKeyInfoViewModel;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.ViewKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.ui.widget.StatusIndicator;
|
||||
import org.sufficientlysecure.keychain.ui.widget.StatusIndicator.Status;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
public class LinkedIdCreateGithubFragment extends CryptoOperationFragment<SaveKeyringParcel,EditKeyResult> {
|
||||
|
||||
public static final String ARG_GITHUB_COOKIE = "github_cookie";
|
||||
private Button mRetryButton;
|
||||
|
||||
enum State {
|
||||
IDLE, AUTH_PROCESS, AUTH_ERROR, POST_PROCESS, POST_ERROR, LID_PROCESS, LID_ERROR, DONE
|
||||
}
|
||||
|
||||
ViewAnimator mButtonContainer;
|
||||
|
||||
StatusIndicator mStatus1, mStatus2, mStatus3;
|
||||
|
||||
byte[] mFingerprint;
|
||||
long mMasterKeyId;
|
||||
private SaveKeyringParcel.Builder mSkpBuilder;
|
||||
private TextView mLinkedIdTitle, mLinkedIdComment;
|
||||
private boolean mFinishOnStop;
|
||||
|
||||
public static LinkedIdCreateGithubFragment newInstance() {
|
||||
return new LinkedIdCreateGithubFragment();
|
||||
}
|
||||
|
||||
public LinkedIdCreateGithubFragment() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.linked_create_github_fragment, container, false);
|
||||
|
||||
mButtonContainer = view.findViewById(R.id.button_container);
|
||||
|
||||
mStatus1 = view.findViewById(R.id.linked_status_step1);
|
||||
mStatus2 = view.findViewById(R.id.linked_status_step2);
|
||||
mStatus3 = view.findViewById(R.id.linked_status_step3);
|
||||
|
||||
mRetryButton = view.findViewById(R.id.button_retry);
|
||||
|
||||
((ImageView) view.findViewById(R.id.linked_id_type_icon)).setImageResource(R.drawable.linked_github);
|
||||
((ImageView) view.findViewById(R.id.linked_id_certified_icon)).setImageResource(R.drawable.octo_link_24dp);
|
||||
mLinkedIdTitle = view.findViewById(R.id.linked_id_title);
|
||||
mLinkedIdComment = view.findViewById(R.id.linked_id_comment);
|
||||
|
||||
view.findViewById(R.id.back_button).setOnClickListener(v -> {
|
||||
LinkedIdWizard activity = (LinkedIdWizard) requireActivity();
|
||||
activity.loadFragment(null, LinkedIdWizard.FRAG_ACTION_TO_LEFT);
|
||||
});
|
||||
|
||||
view.findViewById(R.id.button_send).setOnClickListener(v -> {
|
||||
step1GetOAuthCode();
|
||||
// for animation testing
|
||||
// onCryptoOperationSuccess(null);
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
UnifiedKeyInfoViewModel viewModel = ViewModelProviders.of(requireActivity()).get(UnifiedKeyInfoViewModel.class);
|
||||
viewModel.getUnifiedKeyInfoLiveData(requireContext()).observe(this, this::onLoadUnifiedKeyInfo);
|
||||
}
|
||||
|
||||
private void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) {
|
||||
this.mMasterKeyId = unifiedKeyInfo.master_key_id();
|
||||
this.mFingerprint = unifiedKeyInfo.fingerprint();
|
||||
}
|
||||
|
||||
private void step1GetOAuthCode() {
|
||||
setState(State.AUTH_PROCESS);
|
||||
|
||||
mButtonContainer.setDisplayedChild(1);
|
||||
|
||||
new Handler().postDelayed(
|
||||
() -> oAuthRequest("github.com/login/oauth/authorize", BuildConfig.GITHUB_CLIENT_ID, "gist"), 300);
|
||||
|
||||
}
|
||||
|
||||
private void showRetryForOAuth() {
|
||||
mRetryButton.setOnClickListener(v -> {
|
||||
v.setOnClickListener(null);
|
||||
step1GetOAuthCode();
|
||||
});
|
||||
mButtonContainer.setDisplayedChild(3);
|
||||
|
||||
}
|
||||
|
||||
private void step1GetOAuthToken() {
|
||||
|
||||
if (mOAuthCode == null) {
|
||||
setState(State.AUTH_ERROR);
|
||||
showRetryForOAuth();
|
||||
return;
|
||||
}
|
||||
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String gistText = GithubResource.generate(activity, mFingerprint);
|
||||
|
||||
new AsyncTask<Void,Void,JSONObject>() {
|
||||
|
||||
Exception mException;
|
||||
|
||||
@Override
|
||||
protected JSONObject doInBackground(Void... dummy) {
|
||||
try {
|
||||
|
||||
JSONObject params = new JSONObject();
|
||||
params.put("client_id", BuildConfig.GITHUB_CLIENT_ID);
|
||||
params.put("client_secret", BuildConfig.GITHUB_CLIENT_SECRET);
|
||||
params.put("code", mOAuthCode);
|
||||
params.put("state", mOAuthState);
|
||||
|
||||
return jsonHttpRequest("https://github.com/login/oauth/access_token", params, null);
|
||||
|
||||
} catch (IOException | HttpResultException e) {
|
||||
mException = e;
|
||||
} catch (JSONException e) {
|
||||
throw new AssertionError("json error, this is a bug!");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(JSONObject result) {
|
||||
super.onPostExecute(result);
|
||||
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
// we couldn't show an error anyways
|
||||
return;
|
||||
}
|
||||
|
||||
Timber.d("response: " + result);
|
||||
|
||||
if (result == null || result.optString("access_token", null) == null) {
|
||||
setState(State.AUTH_ERROR);
|
||||
showRetryForOAuth();
|
||||
|
||||
if (result != null) {
|
||||
Notify.create(activity, R.string.linked_error_auth_failed, Style.ERROR).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mException instanceof SocketTimeoutException) {
|
||||
Notify.create(activity, R.string.linked_error_timeout, Style.ERROR).show();
|
||||
} else if (mException instanceof HttpResultException) {
|
||||
Notify.create(activity, activity.getString(R.string.linked_error_http,
|
||||
((HttpResultException) mException).mResponse),
|
||||
Style.ERROR).show();
|
||||
} else if (mException instanceof IOException) {
|
||||
Notify.create(activity, R.string.linked_error_network, Style.ERROR).show();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
step2PostGist(result.optString("access_token"), gistText);
|
||||
|
||||
}
|
||||
}.execute();
|
||||
|
||||
}
|
||||
|
||||
private void step2PostGist(final String accessToken, final String gistText) {
|
||||
|
||||
setState(State.POST_PROCESS);
|
||||
|
||||
new AsyncTask<Void,Void,JSONObject>() {
|
||||
|
||||
Exception mException;
|
||||
|
||||
@Override
|
||||
protected JSONObject doInBackground(Void... dummy) {
|
||||
try {
|
||||
|
||||
long timer = System.currentTimeMillis();
|
||||
|
||||
JSONObject file = new JSONObject();
|
||||
file.put("content", gistText);
|
||||
|
||||
JSONObject files = new JSONObject();
|
||||
files.put("openpgp.txt", file);
|
||||
|
||||
JSONObject params = new JSONObject();
|
||||
params.put("public", true);
|
||||
params.put("description", getString(R.string.linked_gist_description));
|
||||
params.put("files", files);
|
||||
|
||||
JSONObject result = jsonHttpRequest("https://api.github.com/gists", params, accessToken);
|
||||
|
||||
// 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;
|
||||
|
||||
} catch (IOException | HttpResultException e) {
|
||||
mException = e;
|
||||
} catch (JSONException e) {
|
||||
throw new AssertionError("json error, this is a bug!");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(JSONObject result) {
|
||||
super.onPostExecute(result);
|
||||
|
||||
Timber.d("response: " + result);
|
||||
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
// we couldn't show an error anyways
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
setState(State.POST_ERROR);
|
||||
showRetryForOAuth();
|
||||
|
||||
if (mException instanceof SocketTimeoutException) {
|
||||
Notify.create(activity, R.string.linked_error_timeout, Style.ERROR).show();
|
||||
} else if (mException instanceof HttpResultException) {
|
||||
Notify.create(activity, activity.getString(R.string.linked_error_http,
|
||||
((HttpResultException) mException).mResponse),
|
||||
Style.ERROR).show();
|
||||
} else if (mException instanceof IOException) {
|
||||
Notify.create(activity, R.string.linked_error_network, Style.ERROR).show();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
GithubResource resource;
|
||||
|
||||
try {
|
||||
String gistId = result.getString("id");
|
||||
JSONObject owner = result.getJSONObject("owner");
|
||||
String gistLogin = owner.getString("login");
|
||||
|
||||
URI uri = URI.create("https://gist.github.com/" + gistLogin + "/" + gistId);
|
||||
resource = GithubResource.create(uri);
|
||||
} catch (JSONException e) {
|
||||
setState(State.POST_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
View linkedItem = mButtonContainer.getChildAt(2);
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
|
||||
linkedItem.setTransitionName(resource.toUri().toString());
|
||||
}
|
||||
|
||||
// we only need authorization for this one operation, drop it afterwards
|
||||
revokeToken(accessToken);
|
||||
|
||||
step3EditKey(resource);
|
||||
}
|
||||
|
||||
}.execute();
|
||||
|
||||
}
|
||||
|
||||
private void revokeToken(final String token) {
|
||||
|
||||
new AsyncTask<Void,Void,Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... dummy) {
|
||||
try {
|
||||
HttpsURLConnection nection = (HttpsURLConnection) new URL(
|
||||
"https://api.github.com/applications/" + BuildConfig.GITHUB_CLIENT_ID + "/tokens/" + token)
|
||||
.openConnection();
|
||||
nection.setRequestMethod("DELETE");
|
||||
String encoded = Base64.encodeToString(
|
||||
(BuildConfig.GITHUB_CLIENT_ID + ":" + BuildConfig.GITHUB_CLIENT_SECRET).getBytes(), Base64.NO_WRAP);
|
||||
nection.setRequestProperty("Authorization", "Basic " + encoded);
|
||||
nection.connect();
|
||||
} catch (IOException e) {
|
||||
// nvm
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
|
||||
}
|
||||
|
||||
private void step3EditKey(final GithubResource resource) {
|
||||
|
||||
// set item data while we're there
|
||||
{
|
||||
Context context = getActivity();
|
||||
mLinkedIdTitle.setText(resource.getDisplayTitle(context));
|
||||
mLinkedIdComment.setText(resource.getDisplayComment(context));
|
||||
}
|
||||
|
||||
setState(State.LID_PROCESS);
|
||||
|
||||
new Handler().postDelayed(() -> {
|
||||
WrappedUserAttribute ua = LinkedAttribute.fromResource(resource).toUserAttribute();
|
||||
mSkpBuilder = SaveKeyringParcel.buildChangeKeyringParcel(mMasterKeyId, mFingerprint);
|
||||
mSkpBuilder.addUserAttribute(ua);
|
||||
cryptoOperation();
|
||||
}, 250);
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public SaveKeyringParcel createOperationInput() {
|
||||
// if this is null, the cryptoOperation silently aborts - which is what we want in that case
|
||||
return mSkpBuilder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationSuccess(EditKeyResult result) {
|
||||
|
||||
setState(State.DONE);
|
||||
|
||||
mButtonContainer.getInAnimation().setDuration(750);
|
||||
mButtonContainer.setDisplayedChild(2);
|
||||
|
||||
new Handler().postDelayed(() -> {
|
||||
Activity activity = requireActivity();
|
||||
Intent intent = ViewKeyActivity.getViewKeyActivityIntent(requireActivity(), mMasterKeyId);
|
||||
// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
|
||||
intent.putExtra(ViewKeyActivity.EXTRA_LINKED_TRANSITION, true);
|
||||
View linkedItem = mButtonContainer.getChildAt(2);
|
||||
|
||||
Bundle options = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||
activity, linkedItem, linkedItem.getTransitionName()).toBundle();
|
||||
activity.startActivity(intent, options);
|
||||
mFinishOnStop = true;
|
||||
} else {
|
||||
activity.startActivity(intent);
|
||||
activity.finish();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
// cookies are automatically saved, we don't want that
|
||||
CookieManager cookieManager = CookieManager.getInstance();
|
||||
String cookie = cookieManager.getCookie("https://github.com/");
|
||||
outState.putString(ARG_GITHUB_COOKIE, cookie);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
String cookie = savedInstanceState.getString(ARG_GITHUB_COOKIE);
|
||||
CookieManager cookieManager = CookieManager.getInstance();
|
||||
cookieManager.setCookie("https://github.com/", cookie);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
try {
|
||||
// cookies are automatically saved, we don't want that
|
||||
CookieManager cookieManager = CookieManager.getInstance();
|
||||
// noinspection deprecation (replacement is api lvl 21)
|
||||
cookieManager.removeAllCookie();
|
||||
} catch (Exception e) {
|
||||
// no biggie if this fails
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
if (mFinishOnStop) {
|
||||
Activity activity = requireActivity();
|
||||
activity.setResult(Activity.RESULT_OK);
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationError(EditKeyResult result) {
|
||||
result.createNotify(getActivity()).show(this);
|
||||
setState(State.LID_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCryptoOperationCancelled() {
|
||||
mRetryButton.setOnClickListener(v -> {
|
||||
v.setOnClickListener(null);
|
||||
mButtonContainer.setDisplayedChild(1);
|
||||
setState(State.LID_PROCESS);
|
||||
cryptoOperation();
|
||||
});
|
||||
mButtonContainer.setDisplayedChild(3);
|
||||
setState(State.LID_ERROR);
|
||||
}
|
||||
|
||||
private String mOAuthCode, mOAuthState;
|
||||
|
||||
public void oAuthRequest(String hostAndPath, String clientId, String scope) {
|
||||
|
||||
Activity activity = getActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] buf = new byte[16];
|
||||
new Random().nextBytes(buf);
|
||||
mOAuthState = new String(Hex.encode(buf));
|
||||
mOAuthCode = null;
|
||||
|
||||
final Dialog auth_dialog = new Dialog(activity);
|
||||
auth_dialog.setContentView(R.layout.oauth_webview);
|
||||
WebView web = auth_dialog.findViewById(R.id.web_view);
|
||||
web.getSettings().setSaveFormData(false);
|
||||
web.getSettings().setJavaScriptEnabled(true);
|
||||
web.getSettings().setUserAgentString("OpenKeychain " + BuildConfig.VERSION_NAME);
|
||||
web.setWebViewClient(new WebViewClient() {
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
Uri uri = Uri.parse(url);
|
||||
if ("oauth-openkeychain".equals(uri.getScheme())) {
|
||||
|
||||
if (mOAuthCode != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uri.getQueryParameter("error") != null) {
|
||||
Timber.i("got oauth error: " + uri.getQueryParameter("error"));
|
||||
auth_dialog.dismiss();
|
||||
return true;
|
||||
}
|
||||
|
||||
// check if mOAuthState == queryParam[state]
|
||||
mOAuthCode = uri.getQueryParameter("code");
|
||||
|
||||
auth_dialog.dismiss();
|
||||
return true;
|
||||
}
|
||||
// don't surf away from github!
|
||||
if (!"github.com".equals(uri.getHost())) {
|
||||
auth_dialog.dismiss();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
auth_dialog.setTitle(R.string.linked_webview_title_github);
|
||||
auth_dialog.setCancelable(true);
|
||||
auth_dialog.setOnDismissListener(dialog -> step1GetOAuthToken());
|
||||
auth_dialog.show();
|
||||
|
||||
web.loadUrl("https://" + hostAndPath +
|
||||
"?client_id=" + clientId +
|
||||
"&scope=" + scope +
|
||||
"&redirect_uri=oauth-openkeychain://linked/" +
|
||||
"&state=" + mOAuthState);
|
||||
|
||||
}
|
||||
|
||||
public void setState(State state) {
|
||||
switch (state) {
|
||||
case IDLE:
|
||||
mStatus1.setDisplayedChild(Status.IDLE);
|
||||
mStatus2.setDisplayedChild(Status.IDLE);
|
||||
mStatus3.setDisplayedChild(Status.IDLE);
|
||||
break;
|
||||
case AUTH_PROCESS:
|
||||
mStatus1.setDisplayedChild(Status.PROGRESS);
|
||||
mStatus2.setDisplayedChild(Status.IDLE);
|
||||
mStatus3.setDisplayedChild(Status.IDLE);
|
||||
break;
|
||||
case AUTH_ERROR:
|
||||
mStatus1.setDisplayedChild(Status.ERROR);
|
||||
mStatus2.setDisplayedChild(Status.IDLE);
|
||||
mStatus3.setDisplayedChild(Status.IDLE);
|
||||
break;
|
||||
case POST_PROCESS:
|
||||
mStatus1.setDisplayedChild(Status.OK);
|
||||
mStatus2.setDisplayedChild(Status.PROGRESS);
|
||||
mStatus3.setDisplayedChild(Status.IDLE);
|
||||
break;
|
||||
case POST_ERROR:
|
||||
mStatus1.setDisplayedChild(Status.OK);
|
||||
mStatus2.setDisplayedChild(Status.ERROR);
|
||||
mStatus3.setDisplayedChild(Status.IDLE);
|
||||
break;
|
||||
case LID_PROCESS:
|
||||
mStatus1.setDisplayedChild(Status.OK);
|
||||
mStatus2.setDisplayedChild(Status.OK);
|
||||
mStatus3.setDisplayedChild(Status.PROGRESS);
|
||||
break;
|
||||
case LID_ERROR:
|
||||
mStatus1.setDisplayedChild(Status.OK);
|
||||
mStatus2.setDisplayedChild(Status.OK);
|
||||
mStatus3.setDisplayedChild(Status.ERROR);
|
||||
break;
|
||||
case DONE:
|
||||
mStatus1.setDisplayedChild(Status.OK);
|
||||
mStatus2.setDisplayedChild(Status.OK);
|
||||
mStatus3.setDisplayedChild(Status.OK);
|
||||
}
|
||||
}
|
||||
|
||||
private static JSONObject jsonHttpRequest(String url, JSONObject params, String accessToken)
|
||||
throws IOException, HttpResultException {
|
||||
|
||||
HttpsURLConnection nection = (HttpsURLConnection) new URL(url).openConnection();
|
||||
nection.setDoInput(true);
|
||||
nection.setDoOutput(true);
|
||||
nection.setConnectTimeout(3000);
|
||||
nection.setReadTimeout(5000);
|
||||
nection.setRequestProperty("Content-Type", "application/json");
|
||||
nection.setRequestProperty("Accept", "application/json");
|
||||
nection.setRequestProperty("User-Agent", "OpenKeychain " + BuildConfig.VERSION_NAME);
|
||||
if (accessToken != null) {
|
||||
nection.setRequestProperty("Authorization", "token " + accessToken);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
nection.connect();
|
||||
|
||||
OutputStream os = nection.getOutputStream();
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
|
||||
writer.write(params.toString());
|
||||
writer.flush();
|
||||
writer.close();
|
||||
os.close();
|
||||
|
||||
int code = nection.getResponseCode();
|
||||
if (code != HttpsURLConnection.HTTP_CREATED && code != HttpsURLConnection.HTTP_OK) {
|
||||
throw new HttpResultException(nection.getResponseCode(), nection.getResponseMessage());
|
||||
}
|
||||
|
||||
InputStream in = new BufferedInputStream(nection.getInputStream());
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
StringBuilder response = new StringBuilder();
|
||||
while (true) {
|
||||
String line = reader.readLine();
|
||||
if (line == null) {
|
||||
break;
|
||||
}
|
||||
response.append(line);
|
||||
}
|
||||
|
||||
try {
|
||||
return new JSONObject(response.toString());
|
||||
} catch (JSONException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
} finally {
|
||||
nection.disconnect();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class HttpResultException extends Exception {
|
||||
final int mCode;
|
||||
final String mResponse;
|
||||
|
||||
HttpResultException(int code, String response) {
|
||||
mCode = code;
|
||||
mResponse = response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.annotation.NonNull;
|
||||
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.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
|
||||
public class LinkedIdCreateHttpsStep1Fragment extends Fragment {
|
||||
EditText mEditUri;
|
||||
|
||||
public static LinkedIdCreateHttpsStep1Fragment newInstance() {
|
||||
LinkedIdCreateHttpsStep1Fragment frag = new LinkedIdCreateHttpsStep1Fragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull 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(v -> {
|
||||
String uri = "https://" + mEditUri.getText();
|
||||
|
||||
if (!checkUri(uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LinkedIdCreateHttpsStep2Fragment frag = LinkedIdCreateHttpsStep2Fragment.newInstance(uri);
|
||||
|
||||
((LinkedIdWizard) requireActivity()).loadFragment(frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
|
||||
});
|
||||
|
||||
view.findViewById(R.id.back_button).setOnClickListener(
|
||||
v -> ((LinkedIdWizard) requireActivity()).loadFragment(null, LinkedIdWizard.FRAG_ACTION_TO_LEFT));
|
||||
|
||||
mEditUri = view.findViewById(R.id.linked_create_https_uri);
|
||||
|
||||
mEditUri.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
String uri = "https://" + editable;
|
||||
if (uri.length() > 0) {
|
||||
if (checkUri(uri)) {
|
||||
mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.ic_stat_retyped_ok, 0);
|
||||
} else {
|
||||
mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0,
|
||||
R.drawable.ic_stat_retyped_bad, 0);
|
||||
}
|
||||
} else {
|
||||
// remove drawable if email is empty
|
||||
mEditUri.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// mEditUri.setText("mugenguild.com/pgpkey.txt");
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private static boolean checkUri(String uri) {
|
||||
return Patterns.WEB_URL.matcher(uri).matches();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.linked;
|
||||
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
|
||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class LinkedIdCreateHttpsStep2Fragment extends LinkedIdCreateFinalFragment {
|
||||
private static final int REQUEST_CODE_OUTPUT = 0x00007007;
|
||||
|
||||
public static final String ARG_URI = "uri";
|
||||
|
||||
EditText mEditUri;
|
||||
|
||||
URI mResourceUri;
|
||||
|
||||
public static LinkedIdCreateHttpsStep2Fragment newInstance(String uri) {
|
||||
|
||||
LinkedIdCreateHttpsStep2Fragment frag = new LinkedIdCreateHttpsStep2Fragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_URI, uri);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
GenericHttpsResource getResource(OperationLog log) {
|
||||
return GenericHttpsResource.createNew(mResourceUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
try {
|
||||
mResourceUri = new URI(getArguments().getString(ARG_URI));
|
||||
} catch (URISyntaxException e) {
|
||||
Timber.e(e);
|
||||
requireActivity().finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View newView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.linked_create_https_fragment_step2, container, false);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
||||
|
||||
view.findViewById(R.id.button_send).setOnClickListener(v -> proofSend());
|
||||
view.findViewById(R.id.button_save).setOnClickListener(v -> proofSave());
|
||||
|
||||
mEditUri = view.findViewById(R.id.linked_create_https_uri);
|
||||
mEditUri.setText(mResourceUri.toString());
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private String getResourceString() {
|
||||
return GenericHttpsResource.generateText(requireActivity(), fingerprint);
|
||||
}
|
||||
|
||||
private void proofSend() {
|
||||
Intent sendIntent = new Intent();
|
||||
sendIntent.setAction(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, getResourceString());
|
||||
sendIntent.setType("text/plain");
|
||||
startActivity(sendIntent);
|
||||
}
|
||||
|
||||
private void proofSave() {
|
||||
String state = Environment.getExternalStorageState();
|
||||
if (!Environment.MEDIA_MOUNTED.equals(state)) {
|
||||
Notify.create(getActivity(), "External storage not available!", Style.ERROR).show();
|
||||
return;
|
||||
}
|
||||
|
||||
String targetName = "pgpkey.txt";
|
||||
|
||||
// TODO: not supported on Android < 4.4
|
||||
FileHelper.saveDocument(this, targetName, "text/plain", REQUEST_CODE_OUTPUT);
|
||||
}
|
||||
|
||||
private void saveFile(Uri uri) {
|
||||
try {
|
||||
PrintWriter out = new PrintWriter(requireActivity().getContentResolver().openOutputStream(uri));
|
||||
out.print(getResourceString());
|
||||
if (out.checkError()) {
|
||||
Notify.create(getActivity(), "Error writing file!", Style.ERROR).show();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
Notify.create(getActivity(), "File could not be opened for writing!", Style.ERROR).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
// For saving a file
|
||||
case REQUEST_CODE_OUTPUT:
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
Uri uri = data.getData();
|
||||
saveFile(uri);
|
||||
break;
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.annotation.NonNull;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.util.Notify;
|
||||
|
||||
public class LinkedIdCreateTwitterStep1Fragment extends Fragment {
|
||||
EditText mEditHandle;
|
||||
|
||||
public static LinkedIdCreateTwitterStep1Fragment newInstance() {
|
||||
return new LinkedIdCreateTwitterStep1Fragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
final View view = inflater.inflate(R.layout.linked_create_twitter_fragment_step1, container, false);
|
||||
|
||||
view.findViewById(R.id.next_button).setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
final String handle = mEditHandle.getText().toString();
|
||||
|
||||
if ("".equals(handle)) {
|
||||
mEditHandle.setError("Please input a Twitter handle!");
|
||||
return;
|
||||
}
|
||||
|
||||
new AsyncTask<Void,Void,Boolean>() {
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
return true;
|
||||
// return checkHandle(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
super.onPostExecute(result);
|
||||
|
||||
if (result == null) {
|
||||
Notify.create(getActivity(),
|
||||
"Connection error while checking username!",
|
||||
Notify.Style.ERROR).show(LinkedIdCreateTwitterStep1Fragment.this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
Notify.create(getActivity(),
|
||||
"This handle does not exist on Twitter!",
|
||||
Notify.Style.ERROR).show(LinkedIdCreateTwitterStep1Fragment.this);
|
||||
return;
|
||||
}
|
||||
|
||||
LinkedIdCreateTwitterStep2Fragment frag =
|
||||
LinkedIdCreateTwitterStep2Fragment.newInstance(handle);
|
||||
|
||||
((LinkedIdWizard) requireActivity()).loadFragment(frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
|
||||
}
|
||||
}.execute();
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
view.findViewById(R.id.back_button).setOnClickListener(
|
||||
v -> ((LinkedIdWizard) requireActivity()).loadFragment(null, LinkedIdWizard.FRAG_ACTION_TO_LEFT));
|
||||
|
||||
mEditHandle = view.findViewById(R.id.linked_create_twitter_handle);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/* not used at this point, too many problems
|
||||
private static Boolean checkHandle(String handle) {
|
||||
try {
|
||||
HttpURLConnection nection =
|
||||
(HttpURLConnection) new URL("https://twitter.com/" + handle).getUrlResponse();
|
||||
nection.setRequestMethod("HEAD");
|
||||
nection.setRequestProperty("User-Agent", "OpenKeychain");
|
||||
return nection.getResponseCode() == 200;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.linked;
|
||||
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.linked.LinkedTokenResource;
|
||||
import org.sufficientlysecure.keychain.linked.resources.TwitterResource;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
||||
|
||||
public class LinkedIdCreateTwitterStep2Fragment extends LinkedIdCreateFinalFragment {
|
||||
|
||||
public static final String ARG_HANDLE = "handle";
|
||||
|
||||
String mResourceHandle;
|
||||
|
||||
public static LinkedIdCreateTwitterStep2Fragment newInstance(String handle) {
|
||||
LinkedIdCreateTwitterStep2Fragment frag = new LinkedIdCreateTwitterStep2Fragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_HANDLE, handle);
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mResourceHandle = getArguments().getString(ARG_HANDLE);
|
||||
}
|
||||
|
||||
private String getResourceString() {
|
||||
return TwitterResource.generate(fingerprint);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
||||
|
||||
view.findViewById(R.id.button_send).setOnClickListener(v -> proofSend());
|
||||
view.findViewById(R.id.button_share).setOnClickListener(v -> proofShare());
|
||||
|
||||
Spanned tweetText = Html.fromHtml(getString(R.string.linked_create_twitter_2_3, mResourceHandle));
|
||||
((TextView) view.findViewById(R.id.linked_tweet_published)).setText(tweetText);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
LinkedTokenResource getResource(OperationLog log) {
|
||||
return TwitterResource.searchInTwitterStream(getActivity(), mResourceHandle, getResourceString(), log);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View newView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.linked_create_twitter_fragment_step2, container, false);
|
||||
}
|
||||
|
||||
private void proofShare() {
|
||||
Intent sendIntent = new Intent();
|
||||
sendIntent.setAction(Intent.ACTION_SEND);
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, getResourceString());
|
||||
sendIntent.setType("text/plain");
|
||||
startActivity(sendIntent);
|
||||
}
|
||||
|
||||
private void proofSend() {
|
||||
Uri.Builder builder = Uri.parse("https://twitter.com/intent/tweet").buildUpon();
|
||||
builder.appendQueryParameter("text", getResourceString());
|
||||
Uri uri = builder.build();
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.annotation.NonNull;
|
||||
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 {
|
||||
public static LinkedIdSelectFragment newInstance() {
|
||||
return new LinkedIdSelectFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull 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(v -> {
|
||||
LinkedIdCreateHttpsStep1Fragment frag = LinkedIdCreateHttpsStep1Fragment.newInstance();
|
||||
((LinkedIdWizard) requireActivity()).loadFragment(frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
|
||||
});
|
||||
|
||||
view.findViewById(R.id.linked_create_twitter_button).setOnClickListener(v -> {
|
||||
LinkedIdCreateTwitterStep1Fragment frag = LinkedIdCreateTwitterStep1Fragment.newInstance();
|
||||
((LinkedIdWizard) requireActivity()).loadFragment(frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
|
||||
});
|
||||
|
||||
view.findViewById(R.id.linked_create_github_button).setOnClickListener(v -> {
|
||||
LinkedIdCreateGithubFragment frag = LinkedIdCreateGithubFragment.newInstance();
|
||||
((LinkedIdWizard) requireActivity()).loadFragment(frag, LinkedIdWizard.FRAG_ACTION_TO_RIGHT);
|
||||
});
|
||||
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.arch.lifecycle.ViewModelProviders;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.model.SubKey.UnifiedKeyInfo;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.UnifiedKeyInfoViewModel;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
public class LinkedIdWizard extends BaseActivity {
|
||||
public static final String EXTRA_MASTER_KEY_ID = "master_key_id";
|
||||
|
||||
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;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setTitle(getString(R.string.title_linked_id_create));
|
||||
|
||||
Bundle extras = getIntent().getExtras();
|
||||
if (extras == null || !extras.containsKey(EXTRA_MASTER_KEY_ID)) {
|
||||
Timber.e("Missing required extra master_key_id!");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
long masterKeyId = extras.getLong(EXTRA_MASTER_KEY_ID);
|
||||
UnifiedKeyInfoViewModel viewModel = ViewModelProviders.of(this).get(UnifiedKeyInfoViewModel.class);
|
||||
viewModel.setMasterKeyId(masterKeyId);
|
||||
viewModel.getUnifiedKeyInfoLiveData(this).observe(this, this::onLoadUnifiedKeyInfo);
|
||||
|
||||
hideKeyboard();
|
||||
|
||||
// pass extras into fragment
|
||||
if (savedInstanceState == null) {
|
||||
LinkedIdSelectFragment frag = LinkedIdSelectFragment.newInstance();
|
||||
loadFragment(frag, FRAG_ACTION_START);
|
||||
}
|
||||
}
|
||||
|
||||
private void onLoadUnifiedKeyInfo(UnifiedKeyInfo unifiedKeyInfo) {
|
||||
if (!unifiedKeyInfo.has_any_secret()) {
|
||||
Timber.e("Linked Identities can only be added to secret keys!");
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initLayout() {
|
||||
setContentView(R.layout.create_key_activity);
|
||||
}
|
||||
|
||||
public void loadFragment(Fragment fragment, int action) {
|
||||
// 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;
|
||||
|
||||
}
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
private void hideKeyboard() {
|
||||
InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
View v = getCurrentFocus();
|
||||
if (v == null || inputManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user