Merge pull request #5631 from k9mail/convert_RecipientPresenter_to_kotlin
Convert RecipientPresenter to Kotlin
This commit is contained in:
commit
4f290ba8f7
7 changed files with 1492 additions and 1730 deletions
|
@ -58,6 +58,7 @@ dependencies {
|
|||
testImplementation "org.robolectric:robolectric:${versions.robolectric}"
|
||||
testImplementation "androidx.test:core:${versions.androidxTestCore}"
|
||||
testImplementation "junit:junit:${versions.junit}"
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test:${versions.kotlin}"
|
||||
testImplementation "com.google.truth:truth:${versions.truth}"
|
||||
testImplementation "org.mockito:mockito-core:${versions.mockito}"
|
||||
testImplementation "org.mockito.kotlin:mockito-kotlin:${versions.mockitoKotlin}"
|
||||
|
|
|
@ -1,543 +0,0 @@
|
|||
package com.fsck.k9.activity.compose;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.interpolator.view.animation.FastOutLinearInInterpolator;
|
||||
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnFocusChangeListener;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ViewAnimator;
|
||||
|
||||
import com.fsck.k9.FontSizes;
|
||||
import com.fsck.k9.ui.R;
|
||||
import com.fsck.k9.activity.MessageCompose;
|
||||
import com.fsck.k9.mail.Address;
|
||||
import com.fsck.k9.mail.Message.RecipientType;
|
||||
import com.fsck.k9.view.RecipientSelectView;
|
||||
import com.fsck.k9.view.RecipientSelectView.Recipient;
|
||||
import com.fsck.k9.view.RecipientSelectView.TokenListener;
|
||||
import com.fsck.k9.view.ToolableViewAnimator;
|
||||
|
||||
import static com.fsck.k9.FontSizes.FONT_10SP;
|
||||
import static com.fsck.k9.FontSizes.FONT_12SP;
|
||||
import static com.fsck.k9.FontSizes.FONT_16SP;
|
||||
import static com.fsck.k9.FontSizes.FONT_20SP;
|
||||
import static com.fsck.k9.FontSizes.FONT_DEFAULT;
|
||||
import static com.fsck.k9.FontSizes.LARGE;
|
||||
import static com.fsck.k9.FontSizes.MEDIUM;
|
||||
import static com.fsck.k9.FontSizes.SMALL;
|
||||
|
||||
|
||||
public class RecipientMvpView implements OnFocusChangeListener, OnClickListener {
|
||||
private static final int VIEW_INDEX_HIDDEN = -1;
|
||||
|
||||
private static final int VIEW_INDEX_BCC_EXPANDER_VISIBLE = 0;
|
||||
private static final int VIEW_INDEX_BCC_EXPANDER_HIDDEN = 1;
|
||||
|
||||
private static final FastOutLinearInInterpolator CRYPTO_ICON_OUT_ANIMATOR = new FastOutLinearInInterpolator();
|
||||
private static final int CRYPTO_ICON_OUT_DURATION = 195;
|
||||
private static final LinearOutSlowInInterpolator CRYPTO_ICON_IN_ANIMATOR = new LinearOutSlowInInterpolator();
|
||||
private static final int CRYPTO_ICON_IN_DURATION = 225;
|
||||
|
||||
private final MessageCompose activity;
|
||||
private final View ccWrapper;
|
||||
private final View ccDivider;
|
||||
private final View bccWrapper;
|
||||
private final View bccDivider;
|
||||
private final RecipientSelectView toView;
|
||||
private final RecipientSelectView ccView;
|
||||
private final RecipientSelectView bccView;
|
||||
private final ToolableViewAnimator cryptoStatusView;
|
||||
private final ViewAnimator recipientExpanderContainer;
|
||||
private final ToolableViewAnimator cryptoSpecialModeIndicator;
|
||||
private final Set<TextWatcher> textWatchers = new HashSet<>();
|
||||
private RecipientPresenter presenter;
|
||||
|
||||
|
||||
public RecipientMvpView(MessageCompose activity) {
|
||||
this.activity = activity;
|
||||
|
||||
toView = activity.findViewById(R.id.to);
|
||||
ccView = activity.findViewById(R.id.cc);
|
||||
bccView = activity.findViewById(R.id.bcc);
|
||||
ccWrapper = activity.findViewById(R.id.cc_wrapper);
|
||||
ccDivider = activity.findViewById(R.id.cc_divider);
|
||||
bccWrapper = activity.findViewById(R.id.bcc_wrapper);
|
||||
bccDivider = activity.findViewById(R.id.bcc_divider);
|
||||
recipientExpanderContainer = activity.findViewById(R.id.recipient_expander_container);
|
||||
cryptoStatusView = activity.findViewById(R.id.crypto_status);
|
||||
cryptoStatusView.setOnClickListener(this);
|
||||
cryptoSpecialModeIndicator = activity.findViewById(R.id.crypto_special_mode);
|
||||
cryptoSpecialModeIndicator.setOnClickListener(this);
|
||||
|
||||
toView.setOnFocusChangeListener(this);
|
||||
ccView.setOnFocusChangeListener(this);
|
||||
bccView.setOnFocusChangeListener(this);
|
||||
|
||||
View recipientExpander = activity.findViewById(R.id.recipient_expander);
|
||||
recipientExpander.setOnClickListener(this);
|
||||
|
||||
View toLabel = activity.findViewById(R.id.to_label);
|
||||
View ccLabel = activity.findViewById(R.id.cc_label);
|
||||
View bccLabel = activity.findViewById(R.id.bcc_label);
|
||||
toLabel.setOnClickListener(this);
|
||||
ccLabel.setOnClickListener(this);
|
||||
bccLabel.setOnClickListener(this);
|
||||
}
|
||||
|
||||
public void setPresenter(final RecipientPresenter presenter) {
|
||||
this.presenter = presenter;
|
||||
|
||||
if (presenter == null) {
|
||||
toView.setTokenListener(null);
|
||||
ccView.setTokenListener(null);
|
||||
bccView.setTokenListener(null);
|
||||
return;
|
||||
}
|
||||
|
||||
toView.setTokenListener(new TokenListener<Recipient>() {
|
||||
@Override
|
||||
public void onTokenAdded(Recipient recipient) {
|
||||
presenter.onToTokenAdded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTokenRemoved(Recipient recipient) {
|
||||
presenter.onToTokenRemoved();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTokenChanged(Recipient recipient) {
|
||||
presenter.onToTokenChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTokenIgnored(Recipient token) {
|
||||
// Do nothing
|
||||
}
|
||||
});
|
||||
|
||||
ccView.setTokenListener(new TokenListener<Recipient>() {
|
||||
@Override
|
||||
public void onTokenAdded(Recipient recipient) {
|
||||
presenter.onCcTokenAdded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTokenRemoved(Recipient recipient) {
|
||||
presenter.onCcTokenRemoved();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTokenChanged(Recipient recipient) {
|
||||
presenter.onCcTokenChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTokenIgnored(Recipient token) {
|
||||
// Do nothing
|
||||
}
|
||||
});
|
||||
|
||||
bccView.setTokenListener(new TokenListener<Recipient>() {
|
||||
@Override
|
||||
public void onTokenAdded(Recipient recipient) {
|
||||
presenter.onBccTokenAdded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTokenRemoved(Recipient recipient) {
|
||||
presenter.onBccTokenRemoved();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTokenChanged(Recipient recipient) {
|
||||
presenter.onBccTokenChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTokenIgnored(Recipient token) {
|
||||
// Do nothing
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void addTextChangedListener(TextWatcher textWatcher) {
|
||||
textWatchers.add(textWatcher);
|
||||
|
||||
toView.addTextChangedListener(textWatcher);
|
||||
ccView.addTextChangedListener(textWatcher);
|
||||
bccView.addTextChangedListener(textWatcher);
|
||||
}
|
||||
|
||||
private void removeAllTextChangedListeners(TextView view) {
|
||||
for (TextWatcher textWatcher : textWatchers) {
|
||||
view.removeTextChangedListener(textWatcher);
|
||||
}
|
||||
}
|
||||
|
||||
private void addAllTextChangedListeners(TextView view) {
|
||||
for (TextWatcher textWatcher : textWatchers) {
|
||||
view.addTextChangedListener(textWatcher);
|
||||
}
|
||||
}
|
||||
|
||||
public void setRecipientTokensShowCryptoEnabled(boolean isEnabled) {
|
||||
toView.setShowCryptoEnabled(isEnabled);
|
||||
ccView.setShowCryptoEnabled(isEnabled);
|
||||
bccView.setShowCryptoEnabled(isEnabled);
|
||||
}
|
||||
|
||||
public void setCryptoProvider(String openPgpProvider) {
|
||||
// TODO move "show advanced" into settings, or somewhere?
|
||||
toView.setCryptoProvider(openPgpProvider, false);
|
||||
ccView.setCryptoProvider(openPgpProvider, false);
|
||||
bccView.setCryptoProvider(openPgpProvider, false);
|
||||
}
|
||||
|
||||
public void requestFocusOnToField() {
|
||||
toView.requestFocus();
|
||||
}
|
||||
|
||||
public void requestFocusOnCcField() {
|
||||
ccView.requestFocus();
|
||||
}
|
||||
|
||||
public void requestFocusOnBccField() {
|
||||
bccView.requestFocus();
|
||||
}
|
||||
|
||||
public void setFontSizes(FontSizes fontSizes, int fontSize) {
|
||||
int tokenTextSize = getTokenTextSize(fontSize);
|
||||
toView.setTokenTextSize(tokenTextSize);
|
||||
ccView.setTokenTextSize(tokenTextSize);
|
||||
bccView.setTokenTextSize(tokenTextSize);
|
||||
fontSizes.setViewTextSize(toView, fontSize);
|
||||
fontSizes.setViewTextSize(ccView, fontSize);
|
||||
fontSizes.setViewTextSize(bccView, fontSize);
|
||||
}
|
||||
|
||||
private int getTokenTextSize(int fontSize) {
|
||||
switch (fontSize) {
|
||||
case FONT_10SP: return FONT_10SP;
|
||||
case FONT_12SP: return FONT_12SP;
|
||||
case SMALL: return SMALL;
|
||||
case FONT_16SP: return 15;
|
||||
case MEDIUM: return FONT_16SP;
|
||||
case FONT_20SP: return MEDIUM;
|
||||
case LARGE: return FONT_20SP;
|
||||
default: return FONT_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
public void addRecipients(RecipientType recipientType, Recipient... recipients) {
|
||||
switch (recipientType) {
|
||||
case TO: {
|
||||
toView.addRecipients(recipients);
|
||||
break;
|
||||
}
|
||||
case CC: {
|
||||
ccView.addRecipients(recipients);
|
||||
break;
|
||||
}
|
||||
case BCC: {
|
||||
bccView.addRecipients(recipients);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void silentlyAddBccAddresses(Recipient... recipients) {
|
||||
removeAllTextChangedListeners(bccView);
|
||||
|
||||
bccView.addRecipients(recipients);
|
||||
|
||||
addAllTextChangedListeners(bccView);
|
||||
}
|
||||
|
||||
public void silentlyRemoveBccAddresses(Address[] addressesToRemove) {
|
||||
if (addressesToRemove.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<Recipient> bccRecipients = new ArrayList<>(getBccRecipients());
|
||||
for (Recipient recipient : bccRecipients) {
|
||||
removeAllTextChangedListeners(bccView);
|
||||
|
||||
for (Address address : addressesToRemove) {
|
||||
if (recipient.address.equals(address)) {
|
||||
bccView.removeObjectSync(recipient);
|
||||
}
|
||||
}
|
||||
|
||||
addAllTextChangedListeners(bccView);
|
||||
}
|
||||
}
|
||||
|
||||
public void setCcVisibility(boolean visible) {
|
||||
ccWrapper.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
ccDivider.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
public void setBccVisibility(boolean visible) {
|
||||
bccWrapper.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
bccDivider.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
public void setRecipientExpanderVisibility(boolean visible) {
|
||||
int childToDisplay = visible ? VIEW_INDEX_BCC_EXPANDER_VISIBLE : VIEW_INDEX_BCC_EXPANDER_HIDDEN;
|
||||
if (recipientExpanderContainer.getDisplayedChild() != childToDisplay) {
|
||||
recipientExpanderContainer.setDisplayedChild(childToDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCcVisible() {
|
||||
return ccWrapper.getVisibility() == View.VISIBLE;
|
||||
}
|
||||
|
||||
public boolean isBccVisible() {
|
||||
return bccWrapper.getVisibility() == View.VISIBLE;
|
||||
}
|
||||
|
||||
public void showNoRecipientsError() {
|
||||
toView.setError(toView.getContext().getString(R.string.message_compose_error_no_recipients));
|
||||
}
|
||||
|
||||
public List<Address> getToAddresses() {
|
||||
return Arrays.asList(toView.getAddresses());
|
||||
}
|
||||
|
||||
public List<Address> getCcAddresses() {
|
||||
return Arrays.asList(ccView.getAddresses());
|
||||
}
|
||||
|
||||
public List<Address> getBccAddresses() {
|
||||
return Arrays.asList(bccView.getAddresses());
|
||||
}
|
||||
|
||||
public List<Recipient> getToRecipients() {
|
||||
return toView.getObjects();
|
||||
}
|
||||
|
||||
public List<Recipient> getCcRecipients() {
|
||||
return ccView.getObjects();
|
||||
}
|
||||
|
||||
public List<Recipient> getBccRecipients() {
|
||||
return bccView.getObjects();
|
||||
}
|
||||
|
||||
public boolean recipientToHasUncompletedText() {
|
||||
return toView.hasUncompletedText();
|
||||
}
|
||||
|
||||
public boolean recipientCcHasUncompletedText() {
|
||||
return ccView.hasUncompletedText();
|
||||
}
|
||||
|
||||
public boolean recipientBccHasUncompletedText() {
|
||||
return bccView.hasUncompletedText();
|
||||
}
|
||||
|
||||
public boolean recipientToTryPerformCompletion() {
|
||||
return toView.tryPerformCompletion();
|
||||
}
|
||||
|
||||
public boolean recipientCcTryPerformCompletion() {
|
||||
return ccView.tryPerformCompletion();
|
||||
}
|
||||
|
||||
public boolean recipientBccTryPerformCompletion() {
|
||||
return bccView.tryPerformCompletion();
|
||||
}
|
||||
|
||||
public void showToUncompletedError() {
|
||||
toView.setError(toView.getContext().getString(R.string.compose_error_incomplete_recipient));
|
||||
}
|
||||
|
||||
public void showCcUncompletedError() {
|
||||
ccView.setError(ccView.getContext().getString(R.string.compose_error_incomplete_recipient));
|
||||
}
|
||||
|
||||
public void showBccUncompletedError() {
|
||||
bccView.setError(bccView.getContext().getString(R.string.compose_error_incomplete_recipient));
|
||||
}
|
||||
|
||||
public void showCryptoSpecialMode(CryptoSpecialModeDisplayType cryptoSpecialModeDisplayType) {
|
||||
boolean shouldBeHidden = cryptoSpecialModeDisplayType.childIdToDisplay == VIEW_INDEX_HIDDEN;
|
||||
if (shouldBeHidden) {
|
||||
cryptoSpecialModeIndicator.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
cryptoSpecialModeIndicator.setVisibility(View.VISIBLE);
|
||||
cryptoSpecialModeIndicator.setDisplayedChildId(cryptoSpecialModeDisplayType.childIdToDisplay);
|
||||
activity.invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
public void showCryptoStatus(CryptoStatusDisplayType cryptoStatusDisplayType) {
|
||||
boolean shouldBeHidden = cryptoStatusDisplayType.childIdToDisplay == VIEW_INDEX_HIDDEN;
|
||||
if (shouldBeHidden) {
|
||||
cryptoStatusView.animate()
|
||||
.translationXBy(100.0f)
|
||||
.alpha(0.0f)
|
||||
.setDuration(CRYPTO_ICON_OUT_DURATION)
|
||||
.setInterpolator(CRYPTO_ICON_OUT_ANIMATOR)
|
||||
.start();
|
||||
return;
|
||||
}
|
||||
|
||||
cryptoStatusView.setVisibility(View.VISIBLE);
|
||||
cryptoStatusView.setDisplayedChildId(cryptoStatusDisplayType.childIdToDisplay);
|
||||
cryptoStatusView.animate()
|
||||
.translationX(0.0f)
|
||||
.alpha(1.0f)
|
||||
.setDuration(CRYPTO_ICON_IN_DURATION)
|
||||
.setInterpolator(CRYPTO_ICON_IN_ANIMATOR)
|
||||
.start();
|
||||
}
|
||||
|
||||
public void showContactPicker(int requestCode) {
|
||||
activity.showContactPicker(requestCode);
|
||||
}
|
||||
|
||||
public void showErrorIsSignOnly() {
|
||||
Toast.makeText(activity, R.string.error_sign_only_no_encryption, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
public void showErrorContactNoAddress() {
|
||||
Toast.makeText(activity, R.string.error_contact_address_not_found, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
public void showErrorOpenPgpRetrieveStatus() {
|
||||
Toast.makeText(activity, R.string.error_recipient_crypto_retrieve, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
public void showErrorOpenPgpIncompatible() {
|
||||
Toast.makeText(activity, R.string.error_crypto_provider_incompatible, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
public void showErrorOpenPgpConnection() {
|
||||
Toast.makeText(activity, R.string.error_crypto_provider_connect, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
public void showErrorOpenPgpUserInteractionRequired() {
|
||||
Toast.makeText(activity, R.string.error_crypto_provider_ui_required, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
public void showErrorNoKeyConfigured() {
|
||||
Toast.makeText(activity, R.string.compose_error_no_key_configured, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
public void showErrorInlineAttach() {
|
||||
Toast.makeText(activity, R.string.error_crypto_inline_attach, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFocusChange(View view, boolean hasFocus) {
|
||||
if (!hasFocus) {
|
||||
return;
|
||||
}
|
||||
|
||||
int id = view.getId();
|
||||
if (id == R.id.to) {
|
||||
presenter.onToFocused();
|
||||
} else if (id == R.id.cc) {
|
||||
presenter.onCcFocused();
|
||||
} else if (id == R.id.bcc) {
|
||||
presenter.onBccFocused();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
int id = view.getId();
|
||||
if (id == R.id.to_label) {
|
||||
presenter.onClickToLabel();
|
||||
} else if (id == R.id.cc_label) {
|
||||
presenter.onClickCcLabel();
|
||||
} else if (id == R.id.bcc_label) {
|
||||
presenter.onClickBccLabel();
|
||||
} else if (id == R.id.recipient_expander) {
|
||||
presenter.onClickRecipientExpander();
|
||||
} else if (id == R.id.crypto_status) {
|
||||
presenter.onClickCryptoStatus();
|
||||
} else if (id == R.id.crypto_special_mode) {
|
||||
presenter.onClickCryptoSpecialModeIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
public void showOpenPgpInlineDialog(boolean firstTime) {
|
||||
PgpInlineDialog dialog = PgpInlineDialog.newInstance(firstTime, R.id.crypto_special_mode);
|
||||
dialog.show(activity.getSupportFragmentManager(), "openpgp_inline");
|
||||
}
|
||||
|
||||
public void showOpenPgpSignOnlyDialog(boolean firstTime) {
|
||||
PgpSignOnlyDialog dialog = PgpSignOnlyDialog.newInstance(firstTime, R.id.crypto_special_mode);
|
||||
dialog.show(activity.getSupportFragmentManager(), "openpgp_signonly");
|
||||
}
|
||||
|
||||
public void showOpenPgpEnabledErrorDialog(final boolean isGotItDialog) {
|
||||
PgpEnabledErrorDialog dialog = PgpEnabledErrorDialog.newInstance(isGotItDialog, R.id.crypto_status_anchor);
|
||||
dialog.show(activity.getSupportFragmentManager(), "openpgp_error");
|
||||
}
|
||||
|
||||
public void showOpenPgpEncryptExplanationDialog() {
|
||||
PgpEncryptDescriptionDialog dialog = PgpEncryptDescriptionDialog.newInstance(R.id.crypto_status_anchor);
|
||||
dialog.show(activity.getSupportFragmentManager(), "openpgp_description");
|
||||
}
|
||||
|
||||
public void launchUserInteractionPendingIntent(PendingIntent pendingIntent, int requestCode) {
|
||||
activity.launchUserInteractionPendingIntent(pendingIntent, requestCode);
|
||||
}
|
||||
|
||||
public void setLoaderManager(LoaderManager loaderManager) {
|
||||
toView.setLoaderManager(loaderManager);
|
||||
ccView.setLoaderManager(loaderManager);
|
||||
bccView.setLoaderManager(loaderManager);
|
||||
}
|
||||
|
||||
public enum CryptoStatusDisplayType {
|
||||
UNCONFIGURED(VIEW_INDEX_HIDDEN),
|
||||
UNINITIALIZED(VIEW_INDEX_HIDDEN),
|
||||
SIGN_ONLY(R.id.crypto_status_disabled),
|
||||
UNAVAILABLE(VIEW_INDEX_HIDDEN),
|
||||
ENABLED(R.id.crypto_status_enabled),
|
||||
ENABLED_ERROR(R.id.crypto_status_error),
|
||||
ENABLED_TRUSTED(R.id.crypto_status_trusted),
|
||||
AVAILABLE(R.id.crypto_status_disabled),
|
||||
ERROR(R.id.crypto_status_error);
|
||||
|
||||
|
||||
final int childIdToDisplay;
|
||||
|
||||
CryptoStatusDisplayType(int childIdToDisplay) {
|
||||
this.childIdToDisplay = childIdToDisplay;
|
||||
}
|
||||
}
|
||||
|
||||
public enum CryptoSpecialModeDisplayType {
|
||||
NONE(VIEW_INDEX_HIDDEN),
|
||||
PGP_INLINE(R.id.crypto_special_inline),
|
||||
SIGN_ONLY(R.id.crypto_special_sign_only),
|
||||
SIGN_ONLY_PGP_INLINE(R.id.crypto_special_sign_only_inline);
|
||||
|
||||
|
||||
final int childIdToDisplay;
|
||||
|
||||
CryptoSpecialModeDisplayType(int childIdToDisplay) {
|
||||
this.childIdToDisplay = childIdToDisplay;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,414 @@
|
|||
package com.fsck.k9.activity.compose
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import android.widget.ViewAnimator
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.interpolator.view.animation.FastOutLinearInInterpolator
|
||||
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
|
||||
import androidx.loader.app.LoaderManager
|
||||
import com.fsck.k9.FontSizes
|
||||
import com.fsck.k9.activity.MessageCompose
|
||||
import com.fsck.k9.mail.Address
|
||||
import com.fsck.k9.mail.Message.RecipientType
|
||||
import com.fsck.k9.ui.R
|
||||
import com.fsck.k9.view.RecipientSelectView
|
||||
import com.fsck.k9.view.RecipientSelectView.Recipient
|
||||
import com.fsck.k9.view.ToolableViewAnimator
|
||||
import java.lang.AssertionError
|
||||
|
||||
class RecipientMvpView(private val activity: MessageCompose) : View.OnFocusChangeListener, View.OnClickListener {
|
||||
private val toView: RecipientSelectView = activity.findViewById(R.id.to)
|
||||
private val ccView: RecipientSelectView = activity.findViewById(R.id.cc)
|
||||
private val bccView: RecipientSelectView = activity.findViewById(R.id.bcc)
|
||||
private val ccWrapper: View = activity.findViewById(R.id.cc_wrapper)
|
||||
private val ccDivider: View = activity.findViewById(R.id.cc_divider)
|
||||
private val bccWrapper: View = activity.findViewById(R.id.bcc_wrapper)
|
||||
private val bccDivider: View = activity.findViewById(R.id.bcc_divider)
|
||||
private val recipientExpanderContainer: ViewAnimator = activity.findViewById(R.id.recipient_expander_container)
|
||||
private val cryptoStatusView: ToolableViewAnimator = activity.findViewById(R.id.crypto_status)
|
||||
private val cryptoSpecialModeIndicator: ToolableViewAnimator = activity.findViewById(R.id.crypto_special_mode)
|
||||
private val textWatchers: MutableSet<TextWatcher> = HashSet()
|
||||
private lateinit var presenter: RecipientPresenter
|
||||
|
||||
init {
|
||||
cryptoStatusView.setOnClickListener(this)
|
||||
cryptoSpecialModeIndicator.setOnClickListener(this)
|
||||
toView.onFocusChangeListener = this
|
||||
ccView.onFocusChangeListener = this
|
||||
bccView.onFocusChangeListener = this
|
||||
|
||||
activity.findViewById<View>(R.id.recipient_expander).setOnClickListener(this)
|
||||
activity.findViewById<View>(R.id.to_label).setOnClickListener(this)
|
||||
activity.findViewById<View>(R.id.cc_label).setOnClickListener(this)
|
||||
activity.findViewById<View>(R.id.bcc_label).setOnClickListener(this)
|
||||
}
|
||||
|
||||
val isCcVisible: Boolean
|
||||
get() = ccWrapper.isVisible
|
||||
|
||||
val isBccVisible: Boolean
|
||||
get() = bccWrapper.isVisible
|
||||
|
||||
val toAddresses: List<Address>
|
||||
get() = toView.addresses.toList()
|
||||
|
||||
val ccAddresses: List<Address>
|
||||
get() = ccView.addresses.toList()
|
||||
|
||||
val bccAddresses: List<Address>
|
||||
get() = bccView.addresses.toList()
|
||||
|
||||
val toRecipients: List<Recipient>
|
||||
get() = toView.objects
|
||||
|
||||
val ccRecipients: List<Recipient>
|
||||
get() = ccView.objects
|
||||
|
||||
val bccRecipients: List<Recipient>
|
||||
get() = bccView.objects
|
||||
|
||||
fun setPresenter(presenter: RecipientPresenter) {
|
||||
this.presenter = presenter
|
||||
toView.setTokenListener(object : RecipientSelectView.TokenListener<Recipient> {
|
||||
override fun onTokenAdded(recipient: Recipient) = presenter.onToTokenAdded()
|
||||
|
||||
override fun onTokenRemoved(recipient: Recipient) = presenter.onToTokenRemoved()
|
||||
|
||||
override fun onTokenChanged(recipient: Recipient) = presenter.onToTokenChanged()
|
||||
|
||||
override fun onTokenIgnored(token: Recipient) = Unit
|
||||
})
|
||||
|
||||
ccView.setTokenListener(object : RecipientSelectView.TokenListener<Recipient> {
|
||||
override fun onTokenAdded(recipient: Recipient) = presenter.onCcTokenAdded()
|
||||
|
||||
override fun onTokenRemoved(recipient: Recipient) = presenter.onCcTokenRemoved()
|
||||
|
||||
override fun onTokenChanged(recipient: Recipient) = presenter.onCcTokenChanged()
|
||||
|
||||
override fun onTokenIgnored(token: Recipient) = Unit
|
||||
})
|
||||
|
||||
bccView.setTokenListener(object : RecipientSelectView.TokenListener<Recipient> {
|
||||
override fun onTokenAdded(recipient: Recipient) = presenter.onBccTokenAdded()
|
||||
|
||||
override fun onTokenRemoved(recipient: Recipient) = presenter.onBccTokenRemoved()
|
||||
|
||||
override fun onTokenChanged(recipient: Recipient) = presenter.onBccTokenChanged()
|
||||
|
||||
override fun onTokenIgnored(token: Recipient) = Unit
|
||||
})
|
||||
}
|
||||
|
||||
fun addTextChangedListener(textWatcher: TextWatcher) {
|
||||
textWatchers.add(textWatcher)
|
||||
toView.addTextChangedListener(textWatcher)
|
||||
ccView.addTextChangedListener(textWatcher)
|
||||
bccView.addTextChangedListener(textWatcher)
|
||||
}
|
||||
|
||||
private fun removeAllTextChangedListeners(view: TextView) {
|
||||
for (textWatcher in textWatchers) {
|
||||
view.removeTextChangedListener(textWatcher)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addAllTextChangedListeners(view: TextView) {
|
||||
for (textWatcher in textWatchers) {
|
||||
view.addTextChangedListener(textWatcher)
|
||||
}
|
||||
}
|
||||
|
||||
fun setRecipientTokensShowCryptoEnabled(isEnabled: Boolean) {
|
||||
toView.setShowCryptoEnabled(isEnabled)
|
||||
ccView.setShowCryptoEnabled(isEnabled)
|
||||
bccView.setShowCryptoEnabled(isEnabled)
|
||||
}
|
||||
|
||||
fun setCryptoProvider(openPgpProvider: String?) {
|
||||
// TODO move "show advanced" into settings, or somewhere?
|
||||
toView.setCryptoProvider(openPgpProvider, false)
|
||||
ccView.setCryptoProvider(openPgpProvider, false)
|
||||
bccView.setCryptoProvider(openPgpProvider, false)
|
||||
}
|
||||
|
||||
fun requestFocusOnToField() {
|
||||
toView.requestFocus()
|
||||
}
|
||||
|
||||
fun requestFocusOnCcField() {
|
||||
ccView.requestFocus()
|
||||
}
|
||||
|
||||
fun requestFocusOnBccField() {
|
||||
bccView.requestFocus()
|
||||
}
|
||||
|
||||
fun setFontSizes(fontSizes: FontSizes, fontSize: Int) {
|
||||
val tokenTextSize = getTokenTextSize(fontSize)
|
||||
toView.setTokenTextSize(tokenTextSize)
|
||||
ccView.setTokenTextSize(tokenTextSize)
|
||||
bccView.setTokenTextSize(tokenTextSize)
|
||||
fontSizes.setViewTextSize(toView, fontSize)
|
||||
fontSizes.setViewTextSize(ccView, fontSize)
|
||||
fontSizes.setViewTextSize(bccView, fontSize)
|
||||
}
|
||||
|
||||
private fun getTokenTextSize(fontSize: Int): Int {
|
||||
return when (fontSize) {
|
||||
FontSizes.FONT_10SP -> FontSizes.FONT_10SP
|
||||
FontSizes.FONT_12SP -> FontSizes.FONT_12SP
|
||||
FontSizes.SMALL -> FontSizes.SMALL
|
||||
FontSizes.FONT_16SP -> 15
|
||||
FontSizes.MEDIUM -> FontSizes.FONT_16SP
|
||||
FontSizes.FONT_20SP -> FontSizes.MEDIUM
|
||||
FontSizes.LARGE -> FontSizes.FONT_20SP
|
||||
else -> FontSizes.FONT_DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
fun addRecipients(recipientType: RecipientType, vararg recipients: Recipient) {
|
||||
when (recipientType) {
|
||||
RecipientType.TO -> toView.addRecipients(*recipients)
|
||||
RecipientType.CC -> ccView.addRecipients(*recipients)
|
||||
RecipientType.BCC -> bccView.addRecipients(*recipients)
|
||||
else -> throw AssertionError("Unsupported type: $recipientType")
|
||||
}
|
||||
}
|
||||
|
||||
fun silentlyAddBccAddresses(vararg recipients: Recipient) {
|
||||
removeAllTextChangedListeners(bccView)
|
||||
|
||||
bccView.addRecipients(*recipients)
|
||||
|
||||
addAllTextChangedListeners(bccView)
|
||||
}
|
||||
|
||||
fun silentlyRemoveBccAddresses(addresses: Array<Address>) {
|
||||
if (addresses.isEmpty()) return
|
||||
|
||||
val addressesToRemove = addresses.toSet()
|
||||
for (recipient in bccRecipients.toList()) {
|
||||
removeAllTextChangedListeners(bccView)
|
||||
|
||||
if (recipient.address in addressesToRemove) {
|
||||
bccView.removeObjectSync(recipient)
|
||||
}
|
||||
|
||||
addAllTextChangedListeners(bccView)
|
||||
}
|
||||
}
|
||||
|
||||
fun setCcVisibility(visible: Boolean) {
|
||||
ccWrapper.isVisible = visible
|
||||
ccDivider.isVisible = visible
|
||||
}
|
||||
|
||||
fun setBccVisibility(visible: Boolean) {
|
||||
bccWrapper.isVisible = visible
|
||||
bccDivider.isVisible = visible
|
||||
}
|
||||
|
||||
fun setRecipientExpanderVisibility(visible: Boolean) {
|
||||
val childToDisplay = if (visible) VIEW_INDEX_BCC_EXPANDER_VISIBLE else VIEW_INDEX_BCC_EXPANDER_HIDDEN
|
||||
|
||||
if (recipientExpanderContainer.displayedChild != childToDisplay) {
|
||||
recipientExpanderContainer.displayedChild = childToDisplay
|
||||
}
|
||||
}
|
||||
|
||||
fun showNoRecipientsError() {
|
||||
toView.error = toView.context.getString(R.string.message_compose_error_no_recipients)
|
||||
}
|
||||
|
||||
fun recipientToHasUncompletedText(): Boolean {
|
||||
return toView.hasUncompletedText()
|
||||
}
|
||||
|
||||
fun recipientCcHasUncompletedText(): Boolean {
|
||||
return ccView.hasUncompletedText()
|
||||
}
|
||||
|
||||
fun recipientBccHasUncompletedText(): Boolean {
|
||||
return bccView.hasUncompletedText()
|
||||
}
|
||||
|
||||
fun recipientToTryPerformCompletion(): Boolean {
|
||||
return toView.tryPerformCompletion()
|
||||
}
|
||||
|
||||
fun recipientCcTryPerformCompletion(): Boolean {
|
||||
return ccView.tryPerformCompletion()
|
||||
}
|
||||
|
||||
fun recipientBccTryPerformCompletion(): Boolean {
|
||||
return bccView.tryPerformCompletion()
|
||||
}
|
||||
|
||||
fun showToUncompletedError() {
|
||||
toView.error = toView.context.getString(R.string.compose_error_incomplete_recipient)
|
||||
}
|
||||
|
||||
fun showCcUncompletedError() {
|
||||
ccView.error = ccView.context.getString(R.string.compose_error_incomplete_recipient)
|
||||
}
|
||||
|
||||
fun showBccUncompletedError() {
|
||||
bccView.error = bccView.context.getString(R.string.compose_error_incomplete_recipient)
|
||||
}
|
||||
|
||||
fun showCryptoSpecialMode(cryptoSpecialModeDisplayType: CryptoSpecialModeDisplayType) {
|
||||
val shouldBeHidden = cryptoSpecialModeDisplayType.childIdToDisplay == VIEW_INDEX_HIDDEN
|
||||
if (shouldBeHidden) {
|
||||
cryptoSpecialModeIndicator.isGone = true
|
||||
return
|
||||
}
|
||||
|
||||
cryptoSpecialModeIndicator.isVisible = true
|
||||
cryptoSpecialModeIndicator.displayedChildId = cryptoSpecialModeDisplayType.childIdToDisplay
|
||||
|
||||
activity.invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
fun showCryptoStatus(cryptoStatusDisplayType: CryptoStatusDisplayType) {
|
||||
val shouldBeHidden = cryptoStatusDisplayType.childIdToDisplay == VIEW_INDEX_HIDDEN
|
||||
if (shouldBeHidden) {
|
||||
cryptoStatusView.animate()
|
||||
.translationXBy(100.0f)
|
||||
.alpha(0.0f)
|
||||
.setDuration(CRYPTO_ICON_OUT_DURATION.toLong())
|
||||
.setInterpolator(CRYPTO_ICON_OUT_ANIMATOR)
|
||||
.start()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
cryptoStatusView.isVisible = true
|
||||
cryptoStatusView.displayedChildId = cryptoStatusDisplayType.childIdToDisplay
|
||||
cryptoStatusView.animate()
|
||||
.translationX(0.0f)
|
||||
.alpha(1.0f)
|
||||
.setDuration(CRYPTO_ICON_IN_DURATION.toLong())
|
||||
.setInterpolator(CRYPTO_ICON_IN_ANIMATOR)
|
||||
.start()
|
||||
}
|
||||
|
||||
fun showContactPicker(requestCode: Int) {
|
||||
activity.showContactPicker(requestCode)
|
||||
}
|
||||
|
||||
fun showErrorIsSignOnly() {
|
||||
Toast.makeText(activity, R.string.error_sign_only_no_encryption, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
fun showErrorContactNoAddress() {
|
||||
Toast.makeText(activity, R.string.error_contact_address_not_found, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
fun showErrorOpenPgpIncompatible() {
|
||||
Toast.makeText(activity, R.string.error_crypto_provider_incompatible, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
fun showErrorOpenPgpConnection() {
|
||||
Toast.makeText(activity, R.string.error_crypto_provider_connect, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
fun showErrorOpenPgpUserInteractionRequired() {
|
||||
Toast.makeText(activity, R.string.error_crypto_provider_ui_required, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
fun showErrorNoKeyConfigured() {
|
||||
Toast.makeText(activity, R.string.compose_error_no_key_configured, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
fun showErrorInlineAttach() {
|
||||
Toast.makeText(activity, R.string.error_crypto_inline_attach, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun onFocusChange(view: View, hasFocus: Boolean) {
|
||||
if (!hasFocus) return
|
||||
|
||||
when (view.id) {
|
||||
R.id.to -> presenter.onToFocused()
|
||||
R.id.cc -> presenter.onCcFocused()
|
||||
R.id.bcc -> presenter.onBccFocused()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(view: View) {
|
||||
when (view.id) {
|
||||
R.id.to_label -> presenter.onClickToLabel()
|
||||
R.id.cc_label -> presenter.onClickCcLabel()
|
||||
R.id.bcc_label -> presenter.onClickBccLabel()
|
||||
R.id.recipient_expander -> presenter.onClickRecipientExpander()
|
||||
R.id.crypto_status -> presenter.onClickCryptoStatus()
|
||||
R.id.crypto_special_mode -> presenter.onClickCryptoSpecialModeIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
fun showOpenPgpInlineDialog(firstTime: Boolean) {
|
||||
val dialog = PgpInlineDialog.newInstance(firstTime, R.id.crypto_special_mode)
|
||||
dialog.show(activity.supportFragmentManager, "openpgp_inline")
|
||||
}
|
||||
|
||||
fun showOpenPgpSignOnlyDialog(firstTime: Boolean) {
|
||||
val dialog = PgpSignOnlyDialog.newInstance(firstTime, R.id.crypto_special_mode)
|
||||
dialog.show(activity.supportFragmentManager, "openpgp_signonly")
|
||||
}
|
||||
|
||||
fun showOpenPgpEnabledErrorDialog(isGotItDialog: Boolean) {
|
||||
val dialog = PgpEnabledErrorDialog.newInstance(isGotItDialog, R.id.crypto_status_anchor)
|
||||
dialog.show(activity.supportFragmentManager, "openpgp_error")
|
||||
}
|
||||
|
||||
fun showOpenPgpEncryptExplanationDialog() {
|
||||
val dialog = PgpEncryptDescriptionDialog.newInstance(R.id.crypto_status_anchor)
|
||||
dialog.show(activity.supportFragmentManager, "openpgp_description")
|
||||
}
|
||||
|
||||
fun launchUserInteractionPendingIntent(pendingIntent: PendingIntent?, requestCode: Int) {
|
||||
activity.launchUserInteractionPendingIntent(pendingIntent, requestCode)
|
||||
}
|
||||
|
||||
fun setLoaderManager(loaderManager: LoaderManager?) {
|
||||
toView.setLoaderManager(loaderManager)
|
||||
ccView.setLoaderManager(loaderManager)
|
||||
bccView.setLoaderManager(loaderManager)
|
||||
}
|
||||
|
||||
enum class CryptoStatusDisplayType(val childIdToDisplay: Int) {
|
||||
UNCONFIGURED(VIEW_INDEX_HIDDEN),
|
||||
UNINITIALIZED(VIEW_INDEX_HIDDEN),
|
||||
SIGN_ONLY(R.id.crypto_status_disabled),
|
||||
UNAVAILABLE(VIEW_INDEX_HIDDEN),
|
||||
ENABLED(R.id.crypto_status_enabled),
|
||||
ENABLED_ERROR(R.id.crypto_status_error),
|
||||
ENABLED_TRUSTED(R.id.crypto_status_trusted),
|
||||
AVAILABLE(R.id.crypto_status_disabled),
|
||||
ERROR(R.id.crypto_status_error);
|
||||
}
|
||||
|
||||
enum class CryptoSpecialModeDisplayType(val childIdToDisplay: Int) {
|
||||
NONE(VIEW_INDEX_HIDDEN),
|
||||
PGP_INLINE(R.id.crypto_special_inline),
|
||||
SIGN_ONLY(R.id.crypto_special_sign_only),
|
||||
SIGN_ONLY_PGP_INLINE(R.id.crypto_special_sign_only_inline);
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val VIEW_INDEX_HIDDEN = -1
|
||||
private const val VIEW_INDEX_BCC_EXPANDER_VISIBLE = 0
|
||||
private const val VIEW_INDEX_BCC_EXPANDER_HIDDEN = 1
|
||||
|
||||
private val CRYPTO_ICON_OUT_ANIMATOR = FastOutLinearInInterpolator()
|
||||
private const val CRYPTO_ICON_OUT_DURATION = 195
|
||||
|
||||
private val CRYPTO_ICON_IN_ANIMATOR = LinearOutSlowInInterpolator()
|
||||
private const val CRYPTO_ICON_IN_DURATION = 225
|
||||
}
|
||||
}
|
|
@ -1,886 +0,0 @@
|
|||
package com.fsck.k9.activity.compose;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import android.view.Menu;
|
||||
|
||||
import com.fsck.k9.Account;
|
||||
import com.fsck.k9.Identity;
|
||||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.activity.compose.ComposeCryptoStatus.AttachErrorState;
|
||||
import com.fsck.k9.activity.compose.ComposeCryptoStatus.SendErrorState;
|
||||
import com.fsck.k9.activity.compose.RecipientMvpView.CryptoStatusDisplayType;
|
||||
import com.fsck.k9.autocrypt.AutocryptDraftStateHeader;
|
||||
import com.fsck.k9.autocrypt.AutocryptDraftStateHeaderParser;
|
||||
import com.fsck.k9.helper.Contacts;
|
||||
import com.fsck.k9.helper.MailTo;
|
||||
import com.fsck.k9.helper.ReplyToParser;
|
||||
import com.fsck.k9.helper.ReplyToParser.ReplyToAddresses;
|
||||
import com.fsck.k9.mail.Address;
|
||||
import com.fsck.k9.mail.Flag;
|
||||
import com.fsck.k9.mail.Message;
|
||||
import com.fsck.k9.mail.Message.RecipientType;
|
||||
import com.fsck.k9.message.AutocryptStatusInteractor;
|
||||
import com.fsck.k9.message.AutocryptStatusInteractor.RecipientAutocryptStatus;
|
||||
import com.fsck.k9.message.ComposePgpEnableByDefaultDecider;
|
||||
import com.fsck.k9.message.ComposePgpInlineDecider;
|
||||
import com.fsck.k9.message.MessageBuilder;
|
||||
import com.fsck.k9.message.PgpMessageBuilder;
|
||||
import com.fsck.k9.ui.R;
|
||||
import com.fsck.k9.view.RecipientSelectView.Recipient;
|
||||
import org.openintents.openpgp.OpenPgpApiManager;
|
||||
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpApiManagerCallback;
|
||||
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderError;
|
||||
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderState;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
public class RecipientPresenter {
|
||||
private static final String STATE_KEY_CC_SHOWN = "state:ccShown";
|
||||
private static final String STATE_KEY_BCC_SHOWN = "state:bccShown";
|
||||
private static final String STATE_KEY_LAST_FOCUSED_TYPE = "state:lastFocusedType";
|
||||
private static final String STATE_KEY_CURRENT_CRYPTO_MODE = "state:currentCryptoMode";
|
||||
private static final String STATE_KEY_CRYPTO_ENABLE_PGP_INLINE = "state:cryptoEnablePgpInline";
|
||||
|
||||
private static final int CONTACT_PICKER_TO = 1;
|
||||
private static final int CONTACT_PICKER_CC = 2;
|
||||
private static final int CONTACT_PICKER_BCC = 3;
|
||||
private static final int OPENPGP_USER_INTERACTION = 4;
|
||||
private static final int REQUEST_CODE_AUTOCRYPT = 5;
|
||||
|
||||
private static final int PGP_DIALOG_DISPLAY_THRESHOLD = 2;
|
||||
|
||||
|
||||
// transient state, which is either obtained during construction and initialization, or cached
|
||||
private final Context context;
|
||||
private final RecipientMvpView recipientMvpView;
|
||||
private final ComposePgpEnableByDefaultDecider composePgpEnableByDefaultDecider;
|
||||
private final ComposePgpInlineDecider composePgpInlineDecider;
|
||||
private final AutocryptStatusInteractor autocryptStatusInteractor;
|
||||
private final OpenPgpApiManager openPgpApiManager;
|
||||
private final AutocryptDraftStateHeaderParser draftStateHeaderParser;
|
||||
private ReplyToParser replyToParser;
|
||||
private Account account;
|
||||
private Address[] alwaysBccAddresses;
|
||||
private Boolean hasContactPicker;
|
||||
@Nullable
|
||||
private ComposeCryptoStatus cachedCryptoStatus;
|
||||
|
||||
|
||||
// persistent state, saved during onSaveInstanceState
|
||||
private RecipientType lastFocusedType = RecipientType.TO;
|
||||
private CryptoMode currentCryptoMode = CryptoMode.NO_CHOICE;
|
||||
private boolean cryptoEnablePgpInline = false;
|
||||
private boolean isReplyToEncryptedMessage = false;
|
||||
|
||||
|
||||
public RecipientPresenter(Context context, LoaderManager loaderManager,
|
||||
OpenPgpApiManager openPgpApiManager, RecipientMvpView recipientMvpView, Account account,
|
||||
ComposePgpInlineDecider composePgpInlineDecider,
|
||||
ComposePgpEnableByDefaultDecider composePgpEnableByDefaultDecider,
|
||||
AutocryptStatusInteractor autocryptStatusInteractor,
|
||||
ReplyToParser replyToParser, AutocryptDraftStateHeaderParser draftStateHeaderParser) {
|
||||
this.recipientMvpView = recipientMvpView;
|
||||
this.context = context;
|
||||
this.autocryptStatusInteractor = autocryptStatusInteractor;
|
||||
this.composePgpInlineDecider = composePgpInlineDecider;
|
||||
this.composePgpEnableByDefaultDecider = composePgpEnableByDefaultDecider;
|
||||
this.replyToParser = replyToParser;
|
||||
this.openPgpApiManager = openPgpApiManager;
|
||||
this.draftStateHeaderParser = draftStateHeaderParser;
|
||||
|
||||
recipientMvpView.setPresenter(this);
|
||||
recipientMvpView.setLoaderManager(loaderManager);
|
||||
onSwitchAccount(account);
|
||||
}
|
||||
|
||||
public List<Address> getToAddresses() {
|
||||
return recipientMvpView.getToAddresses();
|
||||
}
|
||||
|
||||
public List<Address> getCcAddresses() {
|
||||
return recipientMvpView.getCcAddresses();
|
||||
}
|
||||
|
||||
public List<Address> getBccAddresses() {
|
||||
return recipientMvpView.getBccAddresses();
|
||||
}
|
||||
|
||||
private List<Recipient> getAllRecipients() {
|
||||
ArrayList<Recipient> result = new ArrayList<>();
|
||||
|
||||
result.addAll(recipientMvpView.getToRecipients());
|
||||
result.addAll(recipientMvpView.getCcRecipients());
|
||||
result.addAll(recipientMvpView.getBccRecipients());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean checkRecipientsOkForSending() {
|
||||
recipientMvpView.recipientToTryPerformCompletion();
|
||||
recipientMvpView.recipientCcTryPerformCompletion();
|
||||
recipientMvpView.recipientBccTryPerformCompletion();
|
||||
|
||||
if (recipientMvpView.recipientToHasUncompletedText()) {
|
||||
recipientMvpView.showToUncompletedError();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (recipientMvpView.recipientCcHasUncompletedText()) {
|
||||
recipientMvpView.showCcUncompletedError();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (recipientMvpView.recipientBccHasUncompletedText()) {
|
||||
recipientMvpView.showBccUncompletedError();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getToAddresses().isEmpty() && getCcAddresses().isEmpty() && getBccAddresses().isEmpty()) {
|
||||
recipientMvpView.showNoRecipientsError();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void initFromReplyToMessage(Message message, boolean isReplyAll) {
|
||||
ReplyToAddresses replyToAddresses = isReplyAll ?
|
||||
replyToParser.getRecipientsToReplyAllTo(message, account) :
|
||||
replyToParser.getRecipientsToReplyTo(message, account);
|
||||
|
||||
addToAddresses(replyToAddresses.to);
|
||||
addCcAddresses(replyToAddresses.cc);
|
||||
|
||||
boolean shouldSendAsPgpInline = composePgpInlineDecider.shouldReplyInline(message);
|
||||
if (shouldSendAsPgpInline) {
|
||||
cryptoEnablePgpInline = true;
|
||||
}
|
||||
|
||||
isReplyToEncryptedMessage = composePgpEnableByDefaultDecider.shouldEncryptByDefault(message);
|
||||
}
|
||||
|
||||
public void initFromTrustIdAction(String trustId) {
|
||||
addToAddresses(Address.parse(trustId));
|
||||
currentCryptoMode = CryptoMode.CHOICE_ENABLED;
|
||||
}
|
||||
|
||||
public void initFromMailto(MailTo mailTo) {
|
||||
addToAddresses(mailTo.getTo());
|
||||
addCcAddresses(mailTo.getCc());
|
||||
addBccAddresses(mailTo.getBcc());
|
||||
}
|
||||
|
||||
public void initFromSendOrViewIntent(Intent intent) {
|
||||
String[] extraEmail = intent.getStringArrayExtra(Intent.EXTRA_EMAIL);
|
||||
String[] extraCc = intent.getStringArrayExtra(Intent.EXTRA_CC);
|
||||
String[] extraBcc = intent.getStringArrayExtra(Intent.EXTRA_BCC);
|
||||
|
||||
if (extraEmail != null) {
|
||||
addToAddresses(addressFromStringArray(extraEmail));
|
||||
}
|
||||
|
||||
if (extraCc != null) {
|
||||
addCcAddresses(addressFromStringArray(extraCc));
|
||||
}
|
||||
|
||||
if (extraBcc != null) {
|
||||
addBccAddresses(addressFromStringArray(extraBcc));
|
||||
}
|
||||
}
|
||||
|
||||
public void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
recipientMvpView.setCcVisibility(savedInstanceState.getBoolean(STATE_KEY_CC_SHOWN));
|
||||
recipientMvpView.setBccVisibility(savedInstanceState.getBoolean(STATE_KEY_BCC_SHOWN));
|
||||
lastFocusedType = RecipientType.valueOf(savedInstanceState.getString(STATE_KEY_LAST_FOCUSED_TYPE));
|
||||
currentCryptoMode = CryptoMode.valueOf(savedInstanceState.getString(STATE_KEY_CURRENT_CRYPTO_MODE));
|
||||
cryptoEnablePgpInline = savedInstanceState.getBoolean(STATE_KEY_CRYPTO_ENABLE_PGP_INLINE);
|
||||
updateRecipientExpanderVisibility();
|
||||
}
|
||||
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
outState.putBoolean(STATE_KEY_CC_SHOWN, recipientMvpView.isCcVisible());
|
||||
outState.putBoolean(STATE_KEY_BCC_SHOWN, recipientMvpView.isBccVisible());
|
||||
outState.putString(STATE_KEY_LAST_FOCUSED_TYPE, lastFocusedType.toString());
|
||||
outState.putString(STATE_KEY_CURRENT_CRYPTO_MODE, currentCryptoMode.toString());
|
||||
outState.putBoolean(STATE_KEY_CRYPTO_ENABLE_PGP_INLINE, cryptoEnablePgpInline);
|
||||
}
|
||||
|
||||
public void initFromDraftMessage(Message message) {
|
||||
initRecipientsFromDraftMessage(message);
|
||||
|
||||
String[] draftStateHeader = message.getHeader(AutocryptDraftStateHeader.AUTOCRYPT_DRAFT_STATE_HEADER);
|
||||
if (draftStateHeader.length == 1) {
|
||||
initEncryptionStateFromDraftStateHeader(draftStateHeader[0]);
|
||||
} else {
|
||||
initPgpInlineFromDraftMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void initEncryptionStateFromDraftStateHeader(String headerValue) {
|
||||
AutocryptDraftStateHeader autocryptDraftStateHeader =
|
||||
draftStateHeaderParser.parseAutocryptDraftStateHeader(headerValue);
|
||||
if (autocryptDraftStateHeader != null) {
|
||||
initEncryptionStateFromDraftStateHeader(autocryptDraftStateHeader);
|
||||
}
|
||||
}
|
||||
|
||||
private void initRecipientsFromDraftMessage(Message message) {
|
||||
addToAddresses(message.getRecipients(RecipientType.TO));
|
||||
|
||||
Address[] ccRecipients = message.getRecipients(RecipientType.CC);
|
||||
addCcAddresses(ccRecipients);
|
||||
|
||||
Address[] bccRecipients = message.getRecipients(RecipientType.BCC);
|
||||
addBccAddresses(bccRecipients);
|
||||
}
|
||||
|
||||
private void initEncryptionStateFromDraftStateHeader(AutocryptDraftStateHeader draftState) {
|
||||
cryptoEnablePgpInline = draftState.isPgpInline();
|
||||
isReplyToEncryptedMessage = draftState.isReply();
|
||||
if (!draftState.isByChoice()) {
|
||||
// TODO if it's not by choice, we're going with our defaults. should we do something here if those differ?
|
||||
return;
|
||||
}
|
||||
|
||||
if (draftState.isSignOnly()) {
|
||||
currentCryptoMode = CryptoMode.SIGN_ONLY;
|
||||
} else {
|
||||
currentCryptoMode = draftState.isEncrypt() ? CryptoMode.CHOICE_ENABLED : CryptoMode.CHOICE_DISABLED;
|
||||
}
|
||||
}
|
||||
|
||||
private void initPgpInlineFromDraftMessage(Message message) {
|
||||
cryptoEnablePgpInline = message.isSet(Flag.X_DRAFT_OPENPGP_INLINE);
|
||||
}
|
||||
|
||||
private void addToAddresses(Address... toAddresses) {
|
||||
addRecipientsFromAddresses(RecipientType.TO, toAddresses);
|
||||
}
|
||||
|
||||
private void addCcAddresses(Address... ccAddresses) {
|
||||
if (ccAddresses.length > 0) {
|
||||
addRecipientsFromAddresses(RecipientType.CC, ccAddresses);
|
||||
recipientMvpView.setCcVisibility(true);
|
||||
updateRecipientExpanderVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
public void addBccAddresses(Address... bccRecipients) {
|
||||
if (bccRecipients.length > 0) {
|
||||
addRecipientsFromAddresses(RecipientType.BCC, bccRecipients);
|
||||
recipientMvpView.setBccVisibility(true);
|
||||
updateRecipientExpanderVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
public void addAlwaysBcc() {
|
||||
alwaysBccAddresses = Address.parse(account.getAlwaysBcc());
|
||||
|
||||
new RecipientLoader(context, account.getOpenPgpProvider(), alwaysBccAddresses) {
|
||||
@Override
|
||||
public void deliverResult(List<Recipient> result) {
|
||||
Recipient[] recipientArray = result.toArray(new Recipient[result.size()]);
|
||||
recipientMvpView.silentlyAddBccAddresses(recipientArray);
|
||||
|
||||
stopLoading();
|
||||
abandon();
|
||||
}
|
||||
}.startLoading();
|
||||
}
|
||||
|
||||
private void removeAlwaysBcc() {
|
||||
if (alwaysBccAddresses != null) {
|
||||
recipientMvpView.silentlyRemoveBccAddresses(alwaysBccAddresses);
|
||||
}
|
||||
}
|
||||
|
||||
public void onPrepareOptionsMenu(Menu menu) {
|
||||
ComposeCryptoStatus currentCryptoStatus = getCurrentCachedCryptoStatus();
|
||||
boolean isCryptoConfigured = currentCryptoStatus != null && currentCryptoStatus.isProviderStateOk();
|
||||
if (isCryptoConfigured) {
|
||||
boolean isEncrypting = currentCryptoStatus.isEncryptionEnabled();
|
||||
menu.findItem(R.id.openpgp_encrypt_enable).setVisible(!isEncrypting);
|
||||
menu.findItem(R.id.openpgp_encrypt_disable).setVisible(isEncrypting);
|
||||
|
||||
boolean showSignOnly = !account.isOpenPgpHideSignOnly();
|
||||
boolean isSignOnly = currentCryptoStatus.isSignOnly();
|
||||
menu.findItem(R.id.openpgp_sign_only).setVisible(showSignOnly && !isSignOnly);
|
||||
menu.findItem(R.id.openpgp_sign_only_disable).setVisible(showSignOnly && isSignOnly);
|
||||
|
||||
boolean pgpInlineModeEnabled = currentCryptoStatus.isPgpInlineModeEnabled();
|
||||
boolean showPgpInlineEnable = (isEncrypting || isSignOnly) && !pgpInlineModeEnabled;
|
||||
menu.findItem(R.id.openpgp_inline_enable).setVisible(showPgpInlineEnable);
|
||||
menu.findItem(R.id.openpgp_inline_disable).setVisible(pgpInlineModeEnabled);
|
||||
} else {
|
||||
menu.findItem(R.id.openpgp_inline_enable).setVisible(false);
|
||||
menu.findItem(R.id.openpgp_inline_disable).setVisible(false);
|
||||
menu.findItem(R.id.openpgp_encrypt_enable).setVisible(false);
|
||||
menu.findItem(R.id.openpgp_encrypt_disable).setVisible(false);
|
||||
menu.findItem(R.id.openpgp_sign_only).setVisible(false);
|
||||
menu.findItem(R.id.openpgp_sign_only_disable).setVisible(false);
|
||||
}
|
||||
|
||||
menu.findItem(R.id.add_from_contacts).setVisible(hasContactPicker() && hasContactPermission());
|
||||
}
|
||||
|
||||
public void onSwitchAccount(Account account) {
|
||||
this.account = account;
|
||||
|
||||
if (account.isAlwaysShowCcBcc()) {
|
||||
recipientMvpView.setCcVisibility(true);
|
||||
recipientMvpView.setBccVisibility(true);
|
||||
updateRecipientExpanderVisibility();
|
||||
}
|
||||
|
||||
removeAlwaysBcc();
|
||||
addAlwaysBcc();
|
||||
|
||||
String openPgpProvider = account.getOpenPgpProvider();
|
||||
recipientMvpView.setCryptoProvider(openPgpProvider);
|
||||
openPgpApiManager.setOpenPgpProvider(openPgpProvider, openPgpCallback);
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedParameters")
|
||||
public void onSwitchIdentity(Identity identity) {
|
||||
|
||||
// TODO decide what actually to do on identity switch?
|
||||
asyncUpdateCryptoStatus();
|
||||
/*
|
||||
if (mIdentityChanged) {
|
||||
mBccWrapper.setVisibility(View.VISIBLE);
|
||||
}
|
||||
mBccView.setText("");
|
||||
mBccView.addAddress(new Address(mAccount.getAlwaysBcc(), ""));
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
private static Address[] addressFromStringArray(String[] addresses) {
|
||||
return addressFromStringArray(Arrays.asList(addresses));
|
||||
}
|
||||
|
||||
private static Address[] addressFromStringArray(List<String> addresses) {
|
||||
ArrayList<Address> result = new ArrayList<>(addresses.size());
|
||||
|
||||
for (String addressStr : addresses) {
|
||||
Collections.addAll(result, Address.parseUnencoded(addressStr));
|
||||
}
|
||||
|
||||
return result.toArray(new Address[result.size()]);
|
||||
}
|
||||
|
||||
void onClickToLabel() {
|
||||
recipientMvpView.requestFocusOnToField();
|
||||
}
|
||||
|
||||
void onClickCcLabel() {
|
||||
recipientMvpView.requestFocusOnCcField();
|
||||
}
|
||||
|
||||
void onClickBccLabel() {
|
||||
recipientMvpView.requestFocusOnBccField();
|
||||
}
|
||||
|
||||
void onClickRecipientExpander() {
|
||||
recipientMvpView.setCcVisibility(true);
|
||||
recipientMvpView.setBccVisibility(true);
|
||||
updateRecipientExpanderVisibility();
|
||||
}
|
||||
|
||||
private void hideEmptyExtendedRecipientFields() {
|
||||
if (recipientMvpView.getCcAddresses().isEmpty()) {
|
||||
recipientMvpView.setCcVisibility(false);
|
||||
if (lastFocusedType == RecipientType.CC) {
|
||||
lastFocusedType = RecipientType.TO;
|
||||
}
|
||||
}
|
||||
if (recipientMvpView.getBccAddresses().isEmpty()) {
|
||||
recipientMvpView.setBccVisibility(false);
|
||||
if (lastFocusedType == RecipientType.BCC) {
|
||||
lastFocusedType = RecipientType.TO;
|
||||
}
|
||||
}
|
||||
updateRecipientExpanderVisibility();
|
||||
}
|
||||
|
||||
private void updateRecipientExpanderVisibility() {
|
||||
boolean notBothAreVisible = !(recipientMvpView.isCcVisible() && recipientMvpView.isBccVisible());
|
||||
recipientMvpView.setRecipientExpanderVisibility(notBothAreVisible);
|
||||
}
|
||||
|
||||
public void asyncUpdateCryptoStatus() {
|
||||
cachedCryptoStatus = null;
|
||||
|
||||
OpenPgpProviderState openPgpProviderState = openPgpApiManager.getOpenPgpProviderState();
|
||||
|
||||
Long accountCryptoKey = account.getOpenPgpKey();
|
||||
if (accountCryptoKey == Account.NO_OPENPGP_KEY) {
|
||||
accountCryptoKey = null;
|
||||
}
|
||||
|
||||
final ComposeCryptoStatus composeCryptoStatus = new ComposeCryptoStatus(
|
||||
openPgpProviderState,
|
||||
accountCryptoKey,
|
||||
getAllRecipients(),
|
||||
cryptoEnablePgpInline,
|
||||
account.getAutocryptPreferEncryptMutual(),
|
||||
isReplyToEncryptedMessage,
|
||||
account.isOpenPgpEncryptAllDrafts(),
|
||||
account.isOpenPgpEncryptSubject(),
|
||||
currentCryptoMode);
|
||||
|
||||
if (openPgpProviderState != OpenPgpProviderState.OK) {
|
||||
cachedCryptoStatus = composeCryptoStatus;
|
||||
redrawCachedCryptoStatusIcon();
|
||||
return;
|
||||
}
|
||||
|
||||
final String[] recipientAddresses = composeCryptoStatus.getRecipientAddressesAsArray();
|
||||
|
||||
new AsyncTask<Void,Void,RecipientAutocryptStatus>() {
|
||||
@Override
|
||||
protected RecipientAutocryptStatus doInBackground(Void... voids) {
|
||||
OpenPgpApi openPgpApi = openPgpApiManager.getOpenPgpApi();
|
||||
if (openPgpApi == null) {
|
||||
return null;
|
||||
}
|
||||
return autocryptStatusInteractor.retrieveCryptoProviderRecipientStatus(openPgpApi, recipientAddresses);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(RecipientAutocryptStatus recipientAutocryptStatus) {
|
||||
if (recipientAutocryptStatus != null) {
|
||||
cachedCryptoStatus = composeCryptoStatus.withRecipientAutocryptStatus(recipientAutocryptStatus);
|
||||
} else {
|
||||
cachedCryptoStatus = composeCryptoStatus;
|
||||
}
|
||||
|
||||
redrawCachedCryptoStatusIcon();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void redrawCachedCryptoStatusIcon() {
|
||||
if (cachedCryptoStatus == null) {
|
||||
throw new IllegalStateException("must have cached crypto status to redraw it!");
|
||||
}
|
||||
|
||||
recipientMvpView.setRecipientTokensShowCryptoEnabled(cachedCryptoStatus.isEncryptionEnabled());
|
||||
|
||||
CryptoStatusDisplayType cryptoStatusDisplayType = cachedCryptoStatus.getDisplayType();
|
||||
recipientMvpView.showCryptoStatus(cryptoStatusDisplayType);
|
||||
recipientMvpView.showCryptoSpecialMode(cachedCryptoStatus.getSpecialModeDisplayType());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ComposeCryptoStatus getCurrentCachedCryptoStatus() {
|
||||
return cachedCryptoStatus;
|
||||
}
|
||||
|
||||
public boolean isForceTextMessageFormat() {
|
||||
return cryptoEnablePgpInline;
|
||||
}
|
||||
|
||||
void onToTokenAdded() {
|
||||
asyncUpdateCryptoStatus();
|
||||
}
|
||||
|
||||
void onToTokenRemoved() {
|
||||
asyncUpdateCryptoStatus();
|
||||
}
|
||||
|
||||
void onToTokenChanged() {
|
||||
asyncUpdateCryptoStatus();
|
||||
}
|
||||
|
||||
void onCcTokenAdded() {
|
||||
asyncUpdateCryptoStatus();
|
||||
}
|
||||
|
||||
void onCcTokenRemoved() {
|
||||
asyncUpdateCryptoStatus();
|
||||
}
|
||||
|
||||
void onCcTokenChanged() {
|
||||
asyncUpdateCryptoStatus();
|
||||
}
|
||||
|
||||
void onBccTokenAdded() {
|
||||
asyncUpdateCryptoStatus();
|
||||
}
|
||||
|
||||
void onBccTokenRemoved() {
|
||||
asyncUpdateCryptoStatus();
|
||||
}
|
||||
|
||||
void onBccTokenChanged() {
|
||||
asyncUpdateCryptoStatus();
|
||||
}
|
||||
|
||||
public void onCryptoModeChanged(CryptoMode cryptoMode) {
|
||||
currentCryptoMode = cryptoMode;
|
||||
asyncUpdateCryptoStatus();
|
||||
}
|
||||
|
||||
public void onCryptoPgpInlineChanged(boolean enablePgpInline) {
|
||||
cryptoEnablePgpInline = enablePgpInline;
|
||||
asyncUpdateCryptoStatus();
|
||||
}
|
||||
|
||||
private void addRecipientsFromAddresses(final RecipientType recipientType, final Address... addresses) {
|
||||
new RecipientLoader(context, account.getOpenPgpProvider(), addresses) {
|
||||
@Override
|
||||
public void deliverResult(List<Recipient> result) {
|
||||
Recipient[] recipientArray = result.toArray(new Recipient[result.size()]);
|
||||
recipientMvpView.addRecipients(recipientType, recipientArray);
|
||||
|
||||
stopLoading();
|
||||
abandon();
|
||||
}
|
||||
}.startLoading();
|
||||
}
|
||||
|
||||
private void addRecipientFromContactUri(final RecipientType recipientType, final Uri uri) {
|
||||
new RecipientLoader(context, account.getOpenPgpProvider(), uri, false) {
|
||||
@Override
|
||||
public void deliverResult(List<Recipient> result) {
|
||||
// TODO handle multiple available mail addresses for a contact?
|
||||
if (result.isEmpty()) {
|
||||
recipientMvpView.showErrorContactNoAddress();
|
||||
return;
|
||||
}
|
||||
|
||||
Recipient recipient = result.get(0);
|
||||
recipientMvpView.addRecipients(recipientType, recipient);
|
||||
|
||||
stopLoading();
|
||||
abandon();
|
||||
}
|
||||
}.startLoading();
|
||||
}
|
||||
|
||||
void onToFocused() {
|
||||
lastFocusedType = RecipientType.TO;
|
||||
}
|
||||
|
||||
void onCcFocused() {
|
||||
lastFocusedType = RecipientType.CC;
|
||||
}
|
||||
|
||||
void onBccFocused() {
|
||||
lastFocusedType = RecipientType.BCC;
|
||||
}
|
||||
|
||||
public void onMenuAddFromContacts() {
|
||||
int requestCode = recipientTypeToRequestCode(lastFocusedType);
|
||||
recipientMvpView.showContactPicker(requestCode);
|
||||
}
|
||||
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case CONTACT_PICKER_TO:
|
||||
case CONTACT_PICKER_CC:
|
||||
case CONTACT_PICKER_BCC:
|
||||
if (resultCode != Activity.RESULT_OK || data == null) {
|
||||
return;
|
||||
}
|
||||
RecipientType recipientType = recipientTypeFromRequestCode(requestCode);
|
||||
addRecipientFromContactUri(recipientType, data.getData());
|
||||
break;
|
||||
case OPENPGP_USER_INTERACTION:
|
||||
openPgpApiManager.onUserInteractionResult();
|
||||
break;
|
||||
case REQUEST_CODE_AUTOCRYPT:
|
||||
asyncUpdateCryptoStatus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static int recipientTypeToRequestCode(RecipientType type) {
|
||||
switch (type) {
|
||||
case TO: {
|
||||
return CONTACT_PICKER_TO;
|
||||
}
|
||||
case CC: {
|
||||
return CONTACT_PICKER_CC;
|
||||
}
|
||||
case BCC: {
|
||||
return CONTACT_PICKER_BCC;
|
||||
}
|
||||
}
|
||||
|
||||
throw new AssertionError("Unhandled case: " + type);
|
||||
}
|
||||
|
||||
private static RecipientType recipientTypeFromRequestCode(int type) {
|
||||
switch (type) {
|
||||
case CONTACT_PICKER_TO: {
|
||||
return RecipientType.TO;
|
||||
}
|
||||
case CONTACT_PICKER_CC: {
|
||||
return RecipientType.CC;
|
||||
}
|
||||
case CONTACT_PICKER_BCC: {
|
||||
return RecipientType.BCC;
|
||||
}
|
||||
}
|
||||
|
||||
throw new AssertionError("Unhandled case: " + type);
|
||||
}
|
||||
|
||||
public void onNonRecipientFieldFocused() {
|
||||
if (!account.isAlwaysShowCcBcc()) {
|
||||
hideEmptyExtendedRecipientFields();
|
||||
}
|
||||
}
|
||||
|
||||
void onClickCryptoStatus() {
|
||||
switch (openPgpApiManager.getOpenPgpProviderState()) {
|
||||
case UNCONFIGURED:
|
||||
Timber.e("click on crypto status while unconfigured - this should not really happen?!");
|
||||
return;
|
||||
case OK:
|
||||
toggleEncryptionState(false);
|
||||
return;
|
||||
case UI_REQUIRED:
|
||||
// TODO show openpgp settings
|
||||
PendingIntent pendingIntent = openPgpApiManager.getUserInteractionPendingIntent();
|
||||
recipientMvpView.launchUserInteractionPendingIntent(pendingIntent, OPENPGP_USER_INTERACTION);
|
||||
break;
|
||||
case UNINITIALIZED:
|
||||
case ERROR:
|
||||
openPgpApiManager.refreshConnection();
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleEncryptionState(boolean showGotIt) {
|
||||
ComposeCryptoStatus currentCryptoStatus = getCurrentCachedCryptoStatus();
|
||||
if (currentCryptoStatus == null) {
|
||||
Timber.e("click on crypto status while crypto status not available - should not really happen?!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentCryptoStatus.isEncryptionEnabled() && !currentCryptoStatus.allRecipientsCanEncrypt()) {
|
||||
recipientMvpView.showOpenPgpEnabledErrorDialog(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentCryptoMode == CryptoMode.SIGN_ONLY) {
|
||||
recipientMvpView.showErrorIsSignOnly();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isEncryptOnNoChoice = currentCryptoStatus.canEncryptAndIsMutualDefault() ||
|
||||
currentCryptoStatus.isReplyToEncrypted();
|
||||
if (currentCryptoMode == CryptoMode.NO_CHOICE) {
|
||||
if (currentCryptoStatus.hasAutocryptPendingIntent()) {
|
||||
recipientMvpView.launchUserInteractionPendingIntent(
|
||||
currentCryptoStatus.getAutocryptPendingIntent(), REQUEST_CODE_AUTOCRYPT);
|
||||
} else if (isEncryptOnNoChoice) {
|
||||
// TODO warning dialog if we override, especially from reply!
|
||||
onCryptoModeChanged(CryptoMode.CHOICE_DISABLED);
|
||||
} else {
|
||||
onCryptoModeChanged(CryptoMode.CHOICE_ENABLED);
|
||||
if (showGotIt) {
|
||||
recipientMvpView.showOpenPgpEncryptExplanationDialog();
|
||||
}
|
||||
}
|
||||
} else if (currentCryptoMode == CryptoMode.CHOICE_DISABLED && !isEncryptOnNoChoice) {
|
||||
onCryptoModeChanged(CryptoMode.CHOICE_ENABLED);
|
||||
} else {
|
||||
onCryptoModeChanged(CryptoMode.NO_CHOICE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the device actually have a Contacts application suitable for
|
||||
* picking a contact. As hard as it is to believe, some vendors ship
|
||||
* without it.
|
||||
*
|
||||
* @return True, if the device supports picking contacts. False, otherwise.
|
||||
*/
|
||||
private boolean hasContactPicker() {
|
||||
if (hasContactPicker == null) {
|
||||
Contacts contacts = Contacts.getInstance(context);
|
||||
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(contacts.contactPickerIntent(), 0);
|
||||
hasContactPicker = !resolveInfoList.isEmpty();
|
||||
}
|
||||
|
||||
return hasContactPicker;
|
||||
}
|
||||
|
||||
private boolean hasContactPermission() {
|
||||
return ContextCompat.checkSelfPermission(context,
|
||||
Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
public void showPgpSendError(SendErrorState sendErrorState) {
|
||||
switch (sendErrorState) {
|
||||
case ENABLED_ERROR:
|
||||
recipientMvpView.showOpenPgpEnabledErrorDialog(false);
|
||||
break;
|
||||
case PROVIDER_ERROR:
|
||||
recipientMvpView.showErrorOpenPgpConnection();
|
||||
break;
|
||||
case KEY_CONFIG_ERROR:
|
||||
recipientMvpView.showErrorNoKeyConfigured();
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("not all error states handled, this is a bug!");
|
||||
}
|
||||
}
|
||||
|
||||
void showPgpAttachError(AttachErrorState attachErrorState) {
|
||||
switch (attachErrorState) {
|
||||
case IS_INLINE:
|
||||
recipientMvpView.showErrorInlineAttach();
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("not all error states handled, this is a bug!");
|
||||
}
|
||||
}
|
||||
|
||||
public void builderSetProperties(MessageBuilder messageBuilder) {
|
||||
if (messageBuilder instanceof PgpMessageBuilder) {
|
||||
throw new IllegalArgumentException("PpgMessageBuilder must be called with ComposeCryptoStatus argument!");
|
||||
}
|
||||
|
||||
messageBuilder.setTo(getToAddresses());
|
||||
messageBuilder.setCc(getCcAddresses());
|
||||
messageBuilder.setBcc(getBccAddresses());
|
||||
}
|
||||
|
||||
public void builderSetProperties(PgpMessageBuilder pgpMessageBuilder, ComposeCryptoStatus cryptoStatus) {
|
||||
pgpMessageBuilder.setTo(getToAddresses());
|
||||
pgpMessageBuilder.setCc(getCcAddresses());
|
||||
pgpMessageBuilder.setBcc(getBccAddresses());
|
||||
|
||||
pgpMessageBuilder.setOpenPgpApi(openPgpApiManager.getOpenPgpApi());
|
||||
pgpMessageBuilder.setCryptoStatus(cryptoStatus);
|
||||
}
|
||||
|
||||
public void onMenuSetPgpInline(boolean enablePgpInline) {
|
||||
onCryptoPgpInlineChanged(enablePgpInline);
|
||||
if (enablePgpInline) {
|
||||
boolean shouldShowPgpInlineDialog = checkAndIncrementPgpInlineDialogCounter();
|
||||
if (shouldShowPgpInlineDialog) {
|
||||
recipientMvpView.showOpenPgpInlineDialog(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onMenuSetSignOnly(boolean enableSignOnly) {
|
||||
if (enableSignOnly) {
|
||||
onCryptoModeChanged(CryptoMode.SIGN_ONLY);
|
||||
boolean shouldShowPgpSignOnlyDialog = checkAndIncrementPgpSignOnlyDialogCounter();
|
||||
if (shouldShowPgpSignOnlyDialog) {
|
||||
recipientMvpView.showOpenPgpSignOnlyDialog(true);
|
||||
}
|
||||
} else {
|
||||
onCryptoModeChanged(CryptoMode.NO_CHOICE);
|
||||
}
|
||||
}
|
||||
|
||||
public void onMenuToggleEncryption() {
|
||||
toggleEncryptionState(true);
|
||||
}
|
||||
|
||||
public void onCryptoPgpClickDisable() {
|
||||
onCryptoModeChanged(CryptoMode.CHOICE_DISABLED);
|
||||
}
|
||||
|
||||
public void onCryptoPgpSignOnlyDisabled() {
|
||||
onCryptoPgpInlineChanged(false);
|
||||
onCryptoModeChanged(CryptoMode.NO_CHOICE);
|
||||
}
|
||||
|
||||
private boolean checkAndIncrementPgpInlineDialogCounter() {
|
||||
int pgpInlineDialogCounter = K9.getPgpInlineDialogCounter();
|
||||
if (pgpInlineDialogCounter < PGP_DIALOG_DISPLAY_THRESHOLD) {
|
||||
K9.setPgpInlineDialogCounter(pgpInlineDialogCounter + 1);
|
||||
K9.saveSettingsAsync();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean checkAndIncrementPgpSignOnlyDialogCounter() {
|
||||
int pgpSignOnlyDialogCounter = K9.getPgpSignOnlyDialogCounter();
|
||||
if (pgpSignOnlyDialogCounter < PGP_DIALOG_DISPLAY_THRESHOLD) {
|
||||
K9.setPgpSignOnlyDialogCounter(pgpSignOnlyDialogCounter + 1);
|
||||
K9.saveSettingsAsync();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void onClickCryptoSpecialModeIndicator() {
|
||||
if (currentCryptoMode == CryptoMode.SIGN_ONLY) {
|
||||
recipientMvpView.showOpenPgpSignOnlyDialog(false);
|
||||
} else if (cryptoEnablePgpInline) {
|
||||
recipientMvpView.showOpenPgpInlineDialog(false);
|
||||
} else {
|
||||
throw new IllegalStateException("This icon should not be clickable while no special mode is active!");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldSaveRemotely() {
|
||||
// TODO more appropriate logic?
|
||||
return cachedCryptoStatus == null || !cachedCryptoStatus.isEncryptionEnabled();
|
||||
}
|
||||
|
||||
private final OpenPgpApiManagerCallback openPgpCallback = new OpenPgpApiManagerCallback() {
|
||||
@Override
|
||||
public void onOpenPgpProviderStatusChanged() {
|
||||
if (openPgpApiManager.getOpenPgpProviderState() == OpenPgpProviderState.UI_REQUIRED) {
|
||||
recipientMvpView.showErrorOpenPgpUserInteractionRequired();
|
||||
}
|
||||
|
||||
asyncUpdateCryptoStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpenPgpProviderError(OpenPgpProviderError error) {
|
||||
switch (error) {
|
||||
case ConnectionLost:
|
||||
openPgpApiManager.refreshConnection();
|
||||
break;
|
||||
case VersionIncompatible:
|
||||
recipientMvpView.showErrorOpenPgpIncompatible();
|
||||
break;
|
||||
case ConnectionFailed:
|
||||
default:
|
||||
recipientMvpView.showErrorOpenPgpConnection();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public enum CryptoMode {
|
||||
SIGN_ONLY,
|
||||
NO_CHOICE,
|
||||
CHOICE_DISABLED,
|
||||
CHOICE_ENABLED,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,770 @@
|
|||
package com.fsck.k9.activity.compose
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.loader.app.LoaderManager
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.Identity
|
||||
import com.fsck.k9.K9
|
||||
import com.fsck.k9.activity.compose.ComposeCryptoStatus.AttachErrorState
|
||||
import com.fsck.k9.activity.compose.ComposeCryptoStatus.SendErrorState
|
||||
import com.fsck.k9.autocrypt.AutocryptDraftStateHeader
|
||||
import com.fsck.k9.autocrypt.AutocryptDraftStateHeaderParser
|
||||
import com.fsck.k9.helper.Contacts
|
||||
import com.fsck.k9.helper.MailTo
|
||||
import com.fsck.k9.helper.ReplyToParser
|
||||
import com.fsck.k9.mail.Address
|
||||
import com.fsck.k9.mail.Flag
|
||||
import com.fsck.k9.mail.Message
|
||||
import com.fsck.k9.mail.Message.RecipientType
|
||||
import com.fsck.k9.message.AutocryptStatusInteractor
|
||||
import com.fsck.k9.message.AutocryptStatusInteractor.RecipientAutocryptStatus
|
||||
import com.fsck.k9.message.ComposePgpEnableByDefaultDecider
|
||||
import com.fsck.k9.message.ComposePgpInlineDecider
|
||||
import com.fsck.k9.message.MessageBuilder
|
||||
import com.fsck.k9.message.PgpMessageBuilder
|
||||
import com.fsck.k9.ui.R
|
||||
import com.fsck.k9.view.RecipientSelectView.Recipient
|
||||
import org.openintents.openpgp.OpenPgpApiManager
|
||||
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpApiManagerCallback
|
||||
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderError
|
||||
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderState
|
||||
import timber.log.Timber
|
||||
|
||||
private const val STATE_KEY_CC_SHOWN = "state:ccShown"
|
||||
private const val STATE_KEY_BCC_SHOWN = "state:bccShown"
|
||||
private const val STATE_KEY_LAST_FOCUSED_TYPE = "state:lastFocusedType"
|
||||
private const val STATE_KEY_CURRENT_CRYPTO_MODE = "state:currentCryptoMode"
|
||||
private const val STATE_KEY_CRYPTO_ENABLE_PGP_INLINE = "state:cryptoEnablePgpInline"
|
||||
|
||||
private const val CONTACT_PICKER_TO = 1
|
||||
private const val CONTACT_PICKER_CC = 2
|
||||
private const val CONTACT_PICKER_BCC = 3
|
||||
private const val OPENPGP_USER_INTERACTION = 4
|
||||
private const val REQUEST_CODE_AUTOCRYPT = 5
|
||||
|
||||
private const val PGP_DIALOG_DISPLAY_THRESHOLD = 2
|
||||
|
||||
class RecipientPresenter(
|
||||
private val context: Context,
|
||||
loaderManager: LoaderManager,
|
||||
private val openPgpApiManager: OpenPgpApiManager,
|
||||
private val recipientMvpView: RecipientMvpView,
|
||||
account: Account,
|
||||
private val composePgpInlineDecider: ComposePgpInlineDecider,
|
||||
private val composePgpEnableByDefaultDecider: ComposePgpEnableByDefaultDecider,
|
||||
private val autocryptStatusInteractor: AutocryptStatusInteractor,
|
||||
private val replyToParser: ReplyToParser,
|
||||
private val draftStateHeaderParser: AutocryptDraftStateHeaderParser
|
||||
) {
|
||||
private lateinit var account: Account
|
||||
private var alwaysBccAddresses: Array<Address>? = null
|
||||
private var hasContactPicker: Boolean? = null
|
||||
private var isReplyToEncryptedMessage = false
|
||||
|
||||
private var lastFocusedType = RecipientType.TO
|
||||
private var currentCryptoMode = CryptoMode.NO_CHOICE
|
||||
|
||||
var isForceTextMessageFormat = false
|
||||
private set
|
||||
|
||||
var currentCachedCryptoStatus: ComposeCryptoStatus? = null
|
||||
private set
|
||||
|
||||
val toAddresses: List<Address>
|
||||
get() = recipientMvpView.toAddresses
|
||||
|
||||
val ccAddresses: List<Address>
|
||||
get() = recipientMvpView.ccAddresses
|
||||
|
||||
val bccAddresses: List<Address>
|
||||
get() = recipientMvpView.bccAddresses
|
||||
|
||||
private val allRecipients: List<Recipient>
|
||||
get() = with(recipientMvpView) { toRecipients + ccRecipients + bccRecipients }
|
||||
|
||||
init {
|
||||
recipientMvpView.setPresenter(this)
|
||||
recipientMvpView.setLoaderManager(loaderManager)
|
||||
|
||||
onSwitchAccount(account)
|
||||
}
|
||||
|
||||
fun checkRecipientsOkForSending(): Boolean {
|
||||
recipientMvpView.recipientToTryPerformCompletion()
|
||||
recipientMvpView.recipientCcTryPerformCompletion()
|
||||
recipientMvpView.recipientBccTryPerformCompletion()
|
||||
|
||||
if (recipientMvpView.recipientToHasUncompletedText()) {
|
||||
recipientMvpView.showToUncompletedError()
|
||||
return true
|
||||
}
|
||||
|
||||
if (recipientMvpView.recipientCcHasUncompletedText()) {
|
||||
recipientMvpView.showCcUncompletedError()
|
||||
return true
|
||||
}
|
||||
|
||||
if (recipientMvpView.recipientBccHasUncompletedText()) {
|
||||
recipientMvpView.showBccUncompletedError()
|
||||
return true
|
||||
}
|
||||
|
||||
if (toAddresses.isEmpty() && ccAddresses.isEmpty() && bccAddresses.isEmpty()) {
|
||||
recipientMvpView.showNoRecipientsError()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun initFromReplyToMessage(message: Message?, isReplyAll: Boolean) {
|
||||
val replyToAddresses = if (isReplyAll) {
|
||||
replyToParser.getRecipientsToReplyAllTo(message, account)
|
||||
} else {
|
||||
replyToParser.getRecipientsToReplyTo(message, account)
|
||||
}
|
||||
|
||||
addToAddresses(*replyToAddresses.to)
|
||||
addCcAddresses(*replyToAddresses.cc)
|
||||
|
||||
val shouldSendAsPgpInline = composePgpInlineDecider.shouldReplyInline(message)
|
||||
if (shouldSendAsPgpInline) {
|
||||
isForceTextMessageFormat = true
|
||||
}
|
||||
|
||||
isReplyToEncryptedMessage = composePgpEnableByDefaultDecider.shouldEncryptByDefault(message)
|
||||
}
|
||||
|
||||
fun initFromTrustIdAction(trustId: String?) {
|
||||
addToAddresses(*Address.parse(trustId))
|
||||
currentCryptoMode = CryptoMode.CHOICE_ENABLED
|
||||
}
|
||||
|
||||
fun initFromMailto(mailTo: MailTo) {
|
||||
addToAddresses(*mailTo.to)
|
||||
addCcAddresses(*mailTo.cc)
|
||||
addBccAddresses(*mailTo.bcc)
|
||||
}
|
||||
|
||||
fun initFromSendOrViewIntent(intent: Intent) {
|
||||
val toAddresses = intent.getStringArrayExtra(Intent.EXTRA_EMAIL)?.toAddressArray()
|
||||
val ccAddresses = intent.getStringArrayExtra(Intent.EXTRA_CC)?.toAddressArray()
|
||||
val bccAddresses = intent.getStringArrayExtra(Intent.EXTRA_BCC)?.toAddressArray()
|
||||
|
||||
if (toAddresses != null) {
|
||||
addToAddresses(*toAddresses)
|
||||
}
|
||||
|
||||
if (ccAddresses != null) {
|
||||
addCcAddresses(*ccAddresses)
|
||||
}
|
||||
|
||||
if (bccAddresses != null) {
|
||||
addBccAddresses(*bccAddresses)
|
||||
}
|
||||
}
|
||||
|
||||
fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||
recipientMvpView.setCcVisibility(savedInstanceState.getBoolean(STATE_KEY_CC_SHOWN))
|
||||
recipientMvpView.setBccVisibility(savedInstanceState.getBoolean(STATE_KEY_BCC_SHOWN))
|
||||
lastFocusedType = RecipientType.valueOf(savedInstanceState.getString(STATE_KEY_LAST_FOCUSED_TYPE)!!)
|
||||
currentCryptoMode = CryptoMode.valueOf(savedInstanceState.getString(STATE_KEY_CURRENT_CRYPTO_MODE)!!)
|
||||
isForceTextMessageFormat = savedInstanceState.getBoolean(STATE_KEY_CRYPTO_ENABLE_PGP_INLINE)
|
||||
|
||||
updateRecipientExpanderVisibility()
|
||||
}
|
||||
|
||||
fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putBoolean(STATE_KEY_CC_SHOWN, recipientMvpView.isCcVisible)
|
||||
outState.putBoolean(STATE_KEY_BCC_SHOWN, recipientMvpView.isBccVisible)
|
||||
outState.putString(STATE_KEY_LAST_FOCUSED_TYPE, lastFocusedType.toString())
|
||||
outState.putString(STATE_KEY_CURRENT_CRYPTO_MODE, currentCryptoMode.toString())
|
||||
outState.putBoolean(STATE_KEY_CRYPTO_ENABLE_PGP_INLINE, isForceTextMessageFormat)
|
||||
}
|
||||
|
||||
fun initFromDraftMessage(message: Message) {
|
||||
initRecipientsFromDraftMessage(message)
|
||||
|
||||
val draftStateHeader = message.getHeader(AutocryptDraftStateHeader.AUTOCRYPT_DRAFT_STATE_HEADER)
|
||||
if (draftStateHeader.size == 1) {
|
||||
initEncryptionStateFromDraftStateHeader(draftStateHeader.first())
|
||||
} else {
|
||||
initPgpInlineFromDraftMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initEncryptionStateFromDraftStateHeader(headerValue: String) {
|
||||
val autocryptDraftStateHeader = draftStateHeaderParser.parseAutocryptDraftStateHeader(headerValue)
|
||||
if (autocryptDraftStateHeader != null) {
|
||||
initEncryptionStateFromDraftStateHeader(autocryptDraftStateHeader)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initRecipientsFromDraftMessage(message: Message) {
|
||||
addToAddresses(*message.getRecipients(RecipientType.TO))
|
||||
addCcAddresses(*message.getRecipients(RecipientType.CC))
|
||||
addBccAddresses(*message.getRecipients(RecipientType.BCC))
|
||||
}
|
||||
|
||||
private fun initEncryptionStateFromDraftStateHeader(draftState: AutocryptDraftStateHeader) {
|
||||
isForceTextMessageFormat = draftState.isPgpInline
|
||||
isReplyToEncryptedMessage = draftState.isReply
|
||||
|
||||
if (!draftState.isByChoice) {
|
||||
// TODO if it's not by choice, we're going with our defaults. should we do something here if those differ?
|
||||
return
|
||||
}
|
||||
|
||||
currentCryptoMode = when {
|
||||
draftState.isSignOnly -> CryptoMode.SIGN_ONLY
|
||||
draftState.isEncrypt -> CryptoMode.CHOICE_ENABLED
|
||||
else -> CryptoMode.CHOICE_DISABLED
|
||||
}
|
||||
}
|
||||
|
||||
private fun initPgpInlineFromDraftMessage(message: Message) {
|
||||
isForceTextMessageFormat = message.isSet(Flag.X_DRAFT_OPENPGP_INLINE)
|
||||
}
|
||||
|
||||
private fun addToAddresses(vararg toAddresses: Address) {
|
||||
addRecipientsFromAddresses(RecipientType.TO, *toAddresses)
|
||||
}
|
||||
|
||||
private fun addCcAddresses(vararg ccAddresses: Address) {
|
||||
if (ccAddresses.isNotEmpty()) {
|
||||
addRecipientsFromAddresses(RecipientType.CC, *ccAddresses)
|
||||
recipientMvpView.setCcVisibility(true)
|
||||
updateRecipientExpanderVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
private fun addBccAddresses(vararg bccRecipients: Address) {
|
||||
if (bccRecipients.isNotEmpty()) {
|
||||
addRecipientsFromAddresses(RecipientType.BCC, *bccRecipients)
|
||||
recipientMvpView.setBccVisibility(true)
|
||||
updateRecipientExpanderVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
private fun addAlwaysBcc() {
|
||||
val alwaysBccAddresses = Address.parse(account.alwaysBcc)
|
||||
this.alwaysBccAddresses = alwaysBccAddresses
|
||||
if (alwaysBccAddresses.isEmpty()) return
|
||||
|
||||
object : RecipientLoader(context, account.openPgpProvider, *alwaysBccAddresses) {
|
||||
override fun deliverResult(result: List<Recipient>?) {
|
||||
val recipientArray = result!!.toTypedArray()
|
||||
recipientMvpView.silentlyAddBccAddresses(*recipientArray)
|
||||
|
||||
stopLoading()
|
||||
abandon()
|
||||
}
|
||||
}.startLoading()
|
||||
}
|
||||
|
||||
private fun removeAlwaysBcc() {
|
||||
alwaysBccAddresses?.let { alwaysBccAddresses ->
|
||||
recipientMvpView.silentlyRemoveBccAddresses(alwaysBccAddresses)
|
||||
}
|
||||
}
|
||||
|
||||
fun onPrepareOptionsMenu(menu: Menu) {
|
||||
val currentCryptoStatus = currentCachedCryptoStatus
|
||||
|
||||
if (currentCryptoStatus != null && currentCryptoStatus.isProviderStateOk()) {
|
||||
val isEncrypting = currentCryptoStatus.isEncryptionEnabled
|
||||
menu.findItem(R.id.openpgp_encrypt_enable).isVisible = !isEncrypting
|
||||
menu.findItem(R.id.openpgp_encrypt_disable).isVisible = isEncrypting
|
||||
|
||||
val showSignOnly = !account.isOpenPgpHideSignOnly
|
||||
val isSignOnly = currentCryptoStatus.isSignOnly
|
||||
menu.findItem(R.id.openpgp_sign_only).isVisible = showSignOnly && !isSignOnly
|
||||
menu.findItem(R.id.openpgp_sign_only_disable).isVisible = showSignOnly && isSignOnly
|
||||
|
||||
val pgpInlineModeEnabled = currentCryptoStatus.isPgpInlineModeEnabled
|
||||
val showPgpInlineEnable = (isEncrypting || isSignOnly) && !pgpInlineModeEnabled
|
||||
menu.findItem(R.id.openpgp_inline_enable).isVisible = showPgpInlineEnable
|
||||
menu.findItem(R.id.openpgp_inline_disable).isVisible = pgpInlineModeEnabled
|
||||
} else {
|
||||
menu.findItem(R.id.openpgp_inline_enable).isVisible = false
|
||||
menu.findItem(R.id.openpgp_inline_disable).isVisible = false
|
||||
menu.findItem(R.id.openpgp_encrypt_enable).isVisible = false
|
||||
menu.findItem(R.id.openpgp_encrypt_disable).isVisible = false
|
||||
menu.findItem(R.id.openpgp_sign_only).isVisible = false
|
||||
menu.findItem(R.id.openpgp_sign_only_disable).isVisible = false
|
||||
}
|
||||
|
||||
menu.findItem(R.id.add_from_contacts).isVisible = hasContactPermission() && hasContactPicker()
|
||||
}
|
||||
|
||||
fun onSwitchAccount(account: Account) {
|
||||
this.account = account
|
||||
|
||||
if (account.isAlwaysShowCcBcc) {
|
||||
recipientMvpView.setCcVisibility(true)
|
||||
recipientMvpView.setBccVisibility(true)
|
||||
updateRecipientExpanderVisibility()
|
||||
}
|
||||
|
||||
removeAlwaysBcc()
|
||||
addAlwaysBcc()
|
||||
|
||||
val openPgpProvider = account.openPgpProvider
|
||||
recipientMvpView.setCryptoProvider(openPgpProvider)
|
||||
openPgpApiManager.setOpenPgpProvider(openPgpProvider, openPgpCallback)
|
||||
}
|
||||
|
||||
fun onSwitchIdentity(identity: Identity) {
|
||||
// TODO decide what actually to do on identity switch?
|
||||
asyncUpdateCryptoStatus()
|
||||
}
|
||||
|
||||
fun onClickToLabel() {
|
||||
recipientMvpView.requestFocusOnToField()
|
||||
}
|
||||
|
||||
fun onClickCcLabel() {
|
||||
recipientMvpView.requestFocusOnCcField()
|
||||
}
|
||||
|
||||
fun onClickBccLabel() {
|
||||
recipientMvpView.requestFocusOnBccField()
|
||||
}
|
||||
|
||||
fun onClickRecipientExpander() {
|
||||
recipientMvpView.setCcVisibility(true)
|
||||
recipientMvpView.setBccVisibility(true)
|
||||
updateRecipientExpanderVisibility()
|
||||
}
|
||||
|
||||
private fun hideEmptyExtendedRecipientFields() {
|
||||
if (recipientMvpView.ccAddresses.isEmpty()) {
|
||||
recipientMvpView.setCcVisibility(false)
|
||||
if (lastFocusedType == RecipientType.CC) {
|
||||
lastFocusedType = RecipientType.TO
|
||||
}
|
||||
}
|
||||
|
||||
if (recipientMvpView.bccAddresses.isEmpty()) {
|
||||
recipientMvpView.setBccVisibility(false)
|
||||
if (lastFocusedType == RecipientType.BCC) {
|
||||
lastFocusedType = RecipientType.TO
|
||||
}
|
||||
}
|
||||
|
||||
updateRecipientExpanderVisibility()
|
||||
}
|
||||
|
||||
private fun updateRecipientExpanderVisibility() {
|
||||
val notBothAreVisible = !(recipientMvpView.isCcVisible && recipientMvpView.isBccVisible)
|
||||
recipientMvpView.setRecipientExpanderVisibility(notBothAreVisible)
|
||||
}
|
||||
|
||||
fun asyncUpdateCryptoStatus() {
|
||||
currentCachedCryptoStatus = null
|
||||
|
||||
val openPgpProviderState = openPgpApiManager.openPgpProviderState
|
||||
var accountCryptoKey: Long? = account.openPgpKey
|
||||
if (accountCryptoKey == Account.NO_OPENPGP_KEY) {
|
||||
accountCryptoKey = null
|
||||
}
|
||||
|
||||
val composeCryptoStatus = ComposeCryptoStatus(
|
||||
openPgpProviderState = openPgpProviderState,
|
||||
openPgpKeyId = accountCryptoKey,
|
||||
recipientAddresses = allRecipients,
|
||||
isPgpInlineModeEnabled = isForceTextMessageFormat,
|
||||
isSenderPreferEncryptMutual = account.autocryptPreferEncryptMutual,
|
||||
isReplyToEncrypted = isReplyToEncryptedMessage,
|
||||
isEncryptAllDrafts = account.isOpenPgpEncryptAllDrafts,
|
||||
isEncryptSubject = account.isOpenPgpEncryptSubject,
|
||||
cryptoMode = currentCryptoMode
|
||||
)
|
||||
|
||||
if (openPgpProviderState != OpenPgpProviderState.OK) {
|
||||
currentCachedCryptoStatus = composeCryptoStatus
|
||||
redrawCachedCryptoStatusIcon()
|
||||
return
|
||||
}
|
||||
|
||||
val recipientAddresses = composeCryptoStatus.recipientAddressesAsArray
|
||||
object : AsyncTask<Void?, Void?, RecipientAutocryptStatus?>() {
|
||||
override fun doInBackground(vararg params: Void?): RecipientAutocryptStatus? {
|
||||
val openPgpApi = openPgpApiManager.openPgpApi ?: return null
|
||||
return autocryptStatusInteractor.retrieveCryptoProviderRecipientStatus(openPgpApi, recipientAddresses)
|
||||
}
|
||||
|
||||
override fun onPostExecute(recipientAutocryptStatus: RecipientAutocryptStatus?) {
|
||||
currentCachedCryptoStatus = if (recipientAutocryptStatus != null) {
|
||||
composeCryptoStatus.withRecipientAutocryptStatus(recipientAutocryptStatus)
|
||||
} else {
|
||||
composeCryptoStatus
|
||||
}
|
||||
|
||||
redrawCachedCryptoStatusIcon()
|
||||
}
|
||||
}.execute()
|
||||
}
|
||||
|
||||
private fun redrawCachedCryptoStatusIcon() {
|
||||
val cryptoStatus = checkNotNull(currentCachedCryptoStatus) { "must have cached crypto status to redraw it!" }
|
||||
|
||||
recipientMvpView.setRecipientTokensShowCryptoEnabled(cryptoStatus.isEncryptionEnabled)
|
||||
recipientMvpView.showCryptoStatus(cryptoStatus.displayType)
|
||||
recipientMvpView.showCryptoSpecialMode(cryptoStatus.specialModeDisplayType)
|
||||
}
|
||||
|
||||
fun onToTokenAdded() {
|
||||
asyncUpdateCryptoStatus()
|
||||
}
|
||||
|
||||
fun onToTokenRemoved() {
|
||||
asyncUpdateCryptoStatus()
|
||||
}
|
||||
|
||||
fun onToTokenChanged() {
|
||||
asyncUpdateCryptoStatus()
|
||||
}
|
||||
|
||||
fun onCcTokenAdded() {
|
||||
asyncUpdateCryptoStatus()
|
||||
}
|
||||
|
||||
fun onCcTokenRemoved() {
|
||||
asyncUpdateCryptoStatus()
|
||||
}
|
||||
|
||||
fun onCcTokenChanged() {
|
||||
asyncUpdateCryptoStatus()
|
||||
}
|
||||
|
||||
fun onBccTokenAdded() {
|
||||
asyncUpdateCryptoStatus()
|
||||
}
|
||||
|
||||
fun onBccTokenRemoved() {
|
||||
asyncUpdateCryptoStatus()
|
||||
}
|
||||
|
||||
fun onBccTokenChanged() {
|
||||
asyncUpdateCryptoStatus()
|
||||
}
|
||||
|
||||
fun onCryptoModeChanged(cryptoMode: CryptoMode) {
|
||||
currentCryptoMode = cryptoMode
|
||||
asyncUpdateCryptoStatus()
|
||||
}
|
||||
|
||||
fun onCryptoPgpInlineChanged(enablePgpInline: Boolean) {
|
||||
isForceTextMessageFormat = enablePgpInline
|
||||
asyncUpdateCryptoStatus()
|
||||
}
|
||||
|
||||
private fun addRecipientsFromAddresses(recipientType: RecipientType, vararg addresses: Address) {
|
||||
object : RecipientLoader(context, account.openPgpProvider, *addresses) {
|
||||
override fun deliverResult(result: List<Recipient>?) {
|
||||
val recipientArray = result!!.toTypedArray()
|
||||
recipientMvpView.addRecipients(recipientType, *recipientArray)
|
||||
|
||||
stopLoading()
|
||||
abandon()
|
||||
}
|
||||
}.startLoading()
|
||||
}
|
||||
|
||||
private fun addRecipientFromContactUri(recipientType: RecipientType, uri: Uri?) {
|
||||
object : RecipientLoader(context, account.openPgpProvider, uri, false) {
|
||||
override fun deliverResult(result: List<Recipient>?) {
|
||||
// TODO handle multiple available mail addresses for a contact?
|
||||
if (result!!.isEmpty()) {
|
||||
recipientMvpView.showErrorContactNoAddress()
|
||||
return
|
||||
}
|
||||
|
||||
val recipient = result[0]
|
||||
recipientMvpView.addRecipients(recipientType, recipient)
|
||||
|
||||
stopLoading()
|
||||
abandon()
|
||||
}
|
||||
}.startLoading()
|
||||
}
|
||||
|
||||
fun onToFocused() {
|
||||
lastFocusedType = RecipientType.TO
|
||||
}
|
||||
|
||||
fun onCcFocused() {
|
||||
lastFocusedType = RecipientType.CC
|
||||
}
|
||||
|
||||
fun onBccFocused() {
|
||||
lastFocusedType = RecipientType.BCC
|
||||
}
|
||||
|
||||
fun onMenuAddFromContacts() {
|
||||
val requestCode = lastFocusedType.toRequestCode()
|
||||
recipientMvpView.showContactPicker(requestCode)
|
||||
}
|
||||
|
||||
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
when (requestCode) {
|
||||
CONTACT_PICKER_TO, CONTACT_PICKER_CC, CONTACT_PICKER_BCC -> {
|
||||
if (resultCode != Activity.RESULT_OK || data == null) return
|
||||
|
||||
val recipientType = requestCode.toRecipientType()
|
||||
addRecipientFromContactUri(recipientType, data.data)
|
||||
}
|
||||
OPENPGP_USER_INTERACTION -> {
|
||||
openPgpApiManager.onUserInteractionResult()
|
||||
}
|
||||
REQUEST_CODE_AUTOCRYPT -> {
|
||||
asyncUpdateCryptoStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onNonRecipientFieldFocused() {
|
||||
if (!account.isAlwaysShowCcBcc) {
|
||||
hideEmptyExtendedRecipientFields()
|
||||
}
|
||||
}
|
||||
|
||||
fun onClickCryptoStatus() {
|
||||
when (openPgpApiManager.openPgpProviderState) {
|
||||
OpenPgpProviderState.UNCONFIGURED -> {
|
||||
Timber.e("click on crypto status while unconfigured - this should not really happen?!")
|
||||
}
|
||||
OpenPgpProviderState.OK -> {
|
||||
toggleEncryptionState(false)
|
||||
}
|
||||
OpenPgpProviderState.UI_REQUIRED -> {
|
||||
// TODO show openpgp settings
|
||||
val pendingIntent = openPgpApiManager.userInteractionPendingIntent
|
||||
recipientMvpView.launchUserInteractionPendingIntent(pendingIntent, OPENPGP_USER_INTERACTION)
|
||||
}
|
||||
OpenPgpProviderState.UNINITIALIZED, OpenPgpProviderState.ERROR -> {
|
||||
openPgpApiManager.refreshConnection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleEncryptionState(showGotIt: Boolean) {
|
||||
val currentCryptoStatus = currentCachedCryptoStatus
|
||||
if (currentCryptoStatus == null) {
|
||||
Timber.e("click on crypto status while crypto status not available - should not really happen?!")
|
||||
return
|
||||
}
|
||||
|
||||
if (currentCryptoStatus.isEncryptionEnabled && !currentCryptoStatus.allRecipientsCanEncrypt()) {
|
||||
recipientMvpView.showOpenPgpEnabledErrorDialog(false)
|
||||
return
|
||||
}
|
||||
|
||||
if (currentCryptoMode == CryptoMode.SIGN_ONLY) {
|
||||
recipientMvpView.showErrorIsSignOnly()
|
||||
return
|
||||
}
|
||||
|
||||
val isEncryptOnNoChoice = currentCryptoStatus.canEncryptAndIsMutualDefault() ||
|
||||
currentCryptoStatus.isReplyToEncrypted
|
||||
|
||||
if (currentCryptoMode == CryptoMode.NO_CHOICE) {
|
||||
if (currentCryptoStatus.hasAutocryptPendingIntent()) {
|
||||
recipientMvpView.launchUserInteractionPendingIntent(
|
||||
currentCryptoStatus.autocryptPendingIntent, REQUEST_CODE_AUTOCRYPT
|
||||
)
|
||||
} else if (isEncryptOnNoChoice) {
|
||||
// TODO warning dialog if we override, especially from reply!
|
||||
onCryptoModeChanged(CryptoMode.CHOICE_DISABLED)
|
||||
} else {
|
||||
onCryptoModeChanged(CryptoMode.CHOICE_ENABLED)
|
||||
if (showGotIt) {
|
||||
recipientMvpView.showOpenPgpEncryptExplanationDialog()
|
||||
}
|
||||
}
|
||||
} else if (currentCryptoMode == CryptoMode.CHOICE_DISABLED && !isEncryptOnNoChoice) {
|
||||
onCryptoModeChanged(CryptoMode.CHOICE_ENABLED)
|
||||
} else {
|
||||
onCryptoModeChanged(CryptoMode.NO_CHOICE)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the device actually have a Contacts application suitable for picking a contact.
|
||||
* As hard as it is to believe, some vendors ship without it.
|
||||
*/
|
||||
private fun hasContactPicker(): Boolean {
|
||||
return hasContactPicker ?: isContactPickerAvailable().also { hasContactPicker = it }
|
||||
}
|
||||
|
||||
private fun isContactPickerAvailable(): Boolean {
|
||||
val contacts = Contacts.getInstance(context)
|
||||
val resolveInfoList = context.packageManager.queryIntentActivities(contacts.contactPickerIntent(), 0)
|
||||
return resolveInfoList.isNotEmpty()
|
||||
}
|
||||
|
||||
private fun hasContactPermission(): Boolean {
|
||||
val permissionState = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS)
|
||||
return permissionState == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
fun showPgpSendError(sendErrorState: SendErrorState) {
|
||||
when (sendErrorState) {
|
||||
SendErrorState.ENABLED_ERROR -> recipientMvpView.showOpenPgpEnabledErrorDialog(false)
|
||||
SendErrorState.PROVIDER_ERROR -> recipientMvpView.showErrorOpenPgpConnection()
|
||||
SendErrorState.KEY_CONFIG_ERROR -> recipientMvpView.showErrorNoKeyConfigured()
|
||||
else -> throw AssertionError("not all error states handled, this is a bug!")
|
||||
}
|
||||
}
|
||||
|
||||
fun showPgpAttachError(attachErrorState: AttachErrorState) {
|
||||
when (attachErrorState) {
|
||||
AttachErrorState.IS_INLINE -> recipientMvpView.showErrorInlineAttach()
|
||||
else -> throw AssertionError("not all error states handled, this is a bug!")
|
||||
}
|
||||
}
|
||||
|
||||
fun builderSetProperties(messageBuilder: MessageBuilder) {
|
||||
require(messageBuilder !is PgpMessageBuilder) {
|
||||
"PpgMessageBuilder must be called with ComposeCryptoStatus argument!"
|
||||
}
|
||||
|
||||
messageBuilder.setTo(toAddresses)
|
||||
messageBuilder.setCc(ccAddresses)
|
||||
messageBuilder.setBcc(bccAddresses)
|
||||
}
|
||||
|
||||
fun builderSetProperties(pgpMessageBuilder: PgpMessageBuilder, cryptoStatus: ComposeCryptoStatus) {
|
||||
pgpMessageBuilder.setTo(toAddresses)
|
||||
pgpMessageBuilder.setCc(ccAddresses)
|
||||
pgpMessageBuilder.setBcc(bccAddresses)
|
||||
pgpMessageBuilder.setOpenPgpApi(openPgpApiManager.openPgpApi)
|
||||
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
|
||||
}
|
||||
|
||||
fun onMenuSetPgpInline(enablePgpInline: Boolean) {
|
||||
onCryptoPgpInlineChanged(enablePgpInline)
|
||||
|
||||
if (enablePgpInline) {
|
||||
val shouldShowPgpInlineDialog = checkAndIncrementPgpInlineDialogCounter()
|
||||
if (shouldShowPgpInlineDialog) {
|
||||
recipientMvpView.showOpenPgpInlineDialog(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onMenuSetSignOnly(enableSignOnly: Boolean) {
|
||||
if (enableSignOnly) {
|
||||
onCryptoModeChanged(CryptoMode.SIGN_ONLY)
|
||||
|
||||
val shouldShowPgpSignOnlyDialog = checkAndIncrementPgpSignOnlyDialogCounter()
|
||||
if (shouldShowPgpSignOnlyDialog) {
|
||||
recipientMvpView.showOpenPgpSignOnlyDialog(true)
|
||||
}
|
||||
} else {
|
||||
onCryptoModeChanged(CryptoMode.NO_CHOICE)
|
||||
}
|
||||
}
|
||||
|
||||
fun onMenuToggleEncryption() {
|
||||
toggleEncryptionState(true)
|
||||
}
|
||||
|
||||
fun onCryptoPgpClickDisable() {
|
||||
onCryptoModeChanged(CryptoMode.CHOICE_DISABLED)
|
||||
}
|
||||
|
||||
fun onCryptoPgpSignOnlyDisabled() {
|
||||
onCryptoPgpInlineChanged(false)
|
||||
onCryptoModeChanged(CryptoMode.NO_CHOICE)
|
||||
}
|
||||
|
||||
private fun checkAndIncrementPgpInlineDialogCounter(): Boolean {
|
||||
val pgpInlineDialogCounter = K9.pgpInlineDialogCounter
|
||||
if (pgpInlineDialogCounter < PGP_DIALOG_DISPLAY_THRESHOLD) {
|
||||
K9.pgpInlineDialogCounter = pgpInlineDialogCounter + 1
|
||||
K9.saveSettingsAsync()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun checkAndIncrementPgpSignOnlyDialogCounter(): Boolean {
|
||||
val pgpSignOnlyDialogCounter = K9.pgpSignOnlyDialogCounter
|
||||
if (pgpSignOnlyDialogCounter < PGP_DIALOG_DISPLAY_THRESHOLD) {
|
||||
K9.pgpSignOnlyDialogCounter = pgpSignOnlyDialogCounter + 1
|
||||
K9.saveSettingsAsync()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun onClickCryptoSpecialModeIndicator() {
|
||||
when {
|
||||
currentCryptoMode == CryptoMode.SIGN_ONLY -> {
|
||||
recipientMvpView.showOpenPgpSignOnlyDialog(false)
|
||||
}
|
||||
isForceTextMessageFormat -> {
|
||||
recipientMvpView.showOpenPgpInlineDialog(false)
|
||||
}
|
||||
else -> {
|
||||
error("This icon should not be clickable while no special mode is active!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val openPgpCallback = object : OpenPgpApiManagerCallback {
|
||||
override fun onOpenPgpProviderStatusChanged() {
|
||||
if (openPgpApiManager.openPgpProviderState == OpenPgpProviderState.UI_REQUIRED) {
|
||||
recipientMvpView.showErrorOpenPgpUserInteractionRequired()
|
||||
}
|
||||
|
||||
asyncUpdateCryptoStatus()
|
||||
}
|
||||
|
||||
override fun onOpenPgpProviderError(error: OpenPgpProviderError) {
|
||||
when (error) {
|
||||
OpenPgpProviderError.ConnectionLost -> openPgpApiManager.refreshConnection()
|
||||
OpenPgpProviderError.VersionIncompatible -> recipientMvpView.showErrorOpenPgpIncompatible()
|
||||
OpenPgpProviderError.ConnectionFailed -> recipientMvpView.showErrorOpenPgpConnection()
|
||||
else -> recipientMvpView.showErrorOpenPgpConnection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Array<String>.toAddressArray(): Array<Address> {
|
||||
return flatMap { addressString ->
|
||||
Address.parseUnencoded(addressString).toList()
|
||||
}.toTypedArray()
|
||||
}
|
||||
|
||||
private fun RecipientType.toRequestCode(): Int = when (this) {
|
||||
RecipientType.TO -> CONTACT_PICKER_TO
|
||||
RecipientType.CC -> CONTACT_PICKER_CC
|
||||
RecipientType.BCC -> CONTACT_PICKER_BCC
|
||||
else -> throw AssertionError("Unhandled case: $this")
|
||||
}
|
||||
|
||||
private fun Int.toRecipientType(): RecipientType = when (this) {
|
||||
CONTACT_PICKER_TO -> RecipientType.TO
|
||||
CONTACT_PICKER_CC -> RecipientType.CC
|
||||
CONTACT_PICKER_BCC -> RecipientType.BCC
|
||||
else -> throw AssertionError("Unhandled case: $this")
|
||||
}
|
||||
|
||||
enum class CryptoMode {
|
||||
SIGN_ONLY, NO_CHOICE, CHOICE_DISABLED, CHOICE_ENABLED
|
||||
}
|
||||
}
|
|
@ -1,301 +0,0 @@
|
|||
package com.fsck.k9.activity.compose;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
|
||||
import com.fsck.k9.Account;
|
||||
import com.fsck.k9.DI;
|
||||
import com.fsck.k9.K9RobolectricTest;
|
||||
import com.fsck.k9.activity.compose.RecipientMvpView.CryptoSpecialModeDisplayType;
|
||||
import com.fsck.k9.activity.compose.RecipientMvpView.CryptoStatusDisplayType;
|
||||
import com.fsck.k9.activity.compose.RecipientPresenter.CryptoMode;
|
||||
import com.fsck.k9.autocrypt.AutocryptDraftStateHeaderParser;
|
||||
import com.fsck.k9.helper.ReplyToParser;
|
||||
import com.fsck.k9.helper.ReplyToParser.ReplyToAddresses;
|
||||
import com.fsck.k9.mail.Address;
|
||||
import com.fsck.k9.mail.Message;
|
||||
import com.fsck.k9.mail.Message.RecipientType;
|
||||
import com.fsck.k9.message.AutocryptStatusInteractor;
|
||||
import com.fsck.k9.message.AutocryptStatusInteractor.RecipientAutocryptStatus;
|
||||
import com.fsck.k9.message.AutocryptStatusInteractor.RecipientAutocryptStatusType;
|
||||
import com.fsck.k9.message.ComposePgpEnableByDefaultDecider;
|
||||
import com.fsck.k9.message.ComposePgpInlineDecider;
|
||||
import com.fsck.k9.view.RecipientSelectView.Recipient;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.openintents.openpgp.OpenPgpApiManager;
|
||||
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpApiManagerCallback;
|
||||
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderState;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.LooperMode;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Matchers.isNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@LooperMode(LooperMode.Mode.LEGACY)
|
||||
public class RecipientPresenterTest extends K9RobolectricTest {
|
||||
private static final ReplyToAddresses TO_ADDRESSES = new ReplyToAddresses(Address.parse("to@example.org"));
|
||||
private static final List<Address> ALL_TO_ADDRESSES = Arrays.asList(Address.parse("allTo@example.org"));
|
||||
private static final List<Address> ALL_CC_ADDRESSES = Arrays.asList(Address.parse("allCc@example.org"));
|
||||
private static final String CRYPTO_PROVIDER = "crypto_provider";
|
||||
private static final long CRYPTO_KEY_ID = 123L;
|
||||
|
||||
|
||||
private RecipientPresenter recipientPresenter;
|
||||
private ReplyToParser replyToParser;
|
||||
private ComposePgpInlineDecider composePgpInlineDecider;
|
||||
private ComposePgpEnableByDefaultDecider composePgpEnableByDefaultDecider;
|
||||
private Account account;
|
||||
private RecipientMvpView recipientMvpView;
|
||||
private AutocryptStatusInteractor autocryptStatusInteractor;
|
||||
private RecipientAutocryptStatus noRecipientsAutocryptResult;
|
||||
private OpenPgpApiManager openPgpApiManager;
|
||||
private OpenPgpApiManagerCallback openPgpApiManagerCallback;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
Context context = RuntimeEnvironment.application;
|
||||
Robolectric.getBackgroundThreadScheduler().pause();
|
||||
|
||||
recipientMvpView = mock(RecipientMvpView.class);
|
||||
openPgpApiManager = mock(OpenPgpApiManager.class);
|
||||
account = mock(Account.class);
|
||||
composePgpInlineDecider = mock(ComposePgpInlineDecider.class);
|
||||
composePgpEnableByDefaultDecider = mock(ComposePgpEnableByDefaultDecider.class);
|
||||
autocryptStatusInteractor = mock(AutocryptStatusInteractor.class);
|
||||
replyToParser = mock(ReplyToParser.class);
|
||||
LoaderManager loaderManager = mock(LoaderManager.class);
|
||||
|
||||
when(openPgpApiManager.getOpenPgpProviderState()).thenReturn(OpenPgpProviderState.UNCONFIGURED);
|
||||
|
||||
recipientPresenter = new RecipientPresenter(
|
||||
context, loaderManager, openPgpApiManager, recipientMvpView, account, composePgpInlineDecider,
|
||||
composePgpEnableByDefaultDecider, autocryptStatusInteractor, replyToParser,
|
||||
DI.get(AutocryptDraftStateHeaderParser.class)
|
||||
);
|
||||
|
||||
ArgumentCaptor<OpenPgpApiManagerCallback> callbackCaptor = ArgumentCaptor.forClass(OpenPgpApiManagerCallback.class);
|
||||
verify(openPgpApiManager).setOpenPgpProvider(isNull(String.class), callbackCaptor.capture());
|
||||
openPgpApiManagerCallback = callbackCaptor.getValue();
|
||||
|
||||
noRecipientsAutocryptResult = new RecipientAutocryptStatus(RecipientAutocryptStatusType.NO_RECIPIENTS, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("It looks like the support version of AsyncTaskLoader handles background tasks differently")
|
||||
public void testInitFromReplyToMessage() throws Exception {
|
||||
Message message = mock(Message.class);
|
||||
when(replyToParser.getRecipientsToReplyTo(message, account)).thenReturn(TO_ADDRESSES);
|
||||
|
||||
recipientPresenter.initFromReplyToMessage(message, false);
|
||||
runBackgroundTask();
|
||||
|
||||
Recipient toRecipient = new Recipient(TO_ADDRESSES.to[0]);
|
||||
verify(recipientMvpView).addRecipients(eq(RecipientType.TO), eq(toRecipient));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("It looks like the support version of AsyncTaskLoader handles background tasks differently")
|
||||
public void testInitFromReplyToAllMessage() throws Exception {
|
||||
Message message = mock(Message.class);
|
||||
when(replyToParser.getRecipientsToReplyTo(message, account)).thenReturn(TO_ADDRESSES);
|
||||
ReplyToAddresses replyToAddresses = new ReplyToAddresses(ALL_TO_ADDRESSES, ALL_CC_ADDRESSES);
|
||||
when(replyToParser.getRecipientsToReplyAllTo(message, account)).thenReturn(replyToAddresses);
|
||||
|
||||
recipientPresenter.initFromReplyToMessage(message, true);
|
||||
// one for To, one for Cc
|
||||
runBackgroundTask();
|
||||
runBackgroundTask();
|
||||
|
||||
verify(recipientMvpView).addRecipients(eq(RecipientType.TO), any(Recipient.class));
|
||||
verify(recipientMvpView).addRecipients(eq(RecipientType.CC), any(Recipient.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initFromReplyToMessage_shouldCallComposePgpInlineDecider() throws Exception {
|
||||
Message message = mock(Message.class);
|
||||
when(replyToParser.getRecipientsToReplyTo(message, account)).thenReturn(TO_ADDRESSES);
|
||||
|
||||
recipientPresenter.initFromReplyToMessage(message, false);
|
||||
|
||||
verify(composePgpInlineDecider).shouldReplyInline(message);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCurrentCryptoStatus_withoutCryptoProvider() throws Exception {
|
||||
when(openPgpApiManager.getOpenPgpProviderState()).thenReturn(OpenPgpProviderState.UNCONFIGURED);
|
||||
recipientPresenter.asyncUpdateCryptoStatus();
|
||||
|
||||
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
|
||||
|
||||
assertEquals(CryptoStatusDisplayType.UNCONFIGURED, status.getDisplayType());
|
||||
assertEquals(CryptoSpecialModeDisplayType.NONE, status.getSpecialModeDisplayType());
|
||||
assertNull(status.getAttachErrorStateOrNull());
|
||||
assertFalse(status.isProviderStateOk());
|
||||
assertFalse(status.isOpenPgpConfigured());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCurrentCryptoStatus_withCryptoProvider() throws Exception {
|
||||
setupCryptoProvider(noRecipientsAutocryptResult);
|
||||
|
||||
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
|
||||
|
||||
assertEquals(CryptoStatusDisplayType.UNAVAILABLE, status.getDisplayType());
|
||||
assertTrue(status.isProviderStateOk());
|
||||
assertTrue(status.isOpenPgpConfigured());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCurrentCryptoStatus_withOpportunistic() throws Exception {
|
||||
RecipientAutocryptStatus recipientAutocryptStatus = new RecipientAutocryptStatus(
|
||||
RecipientAutocryptStatusType.AVAILABLE_UNCONFIRMED, null);
|
||||
setupCryptoProvider(recipientAutocryptStatus);
|
||||
|
||||
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
|
||||
|
||||
assertEquals(CryptoStatusDisplayType.AVAILABLE, status.getDisplayType());
|
||||
assertTrue(status.isProviderStateOk());
|
||||
assertTrue(status.isOpenPgpConfigured());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCurrentCryptoStatus_withOpportunistic__confirmed() throws Exception {
|
||||
RecipientAutocryptStatus recipientAutocryptStatus = new RecipientAutocryptStatus(
|
||||
RecipientAutocryptStatusType.AVAILABLE_CONFIRMED, null);
|
||||
setupCryptoProvider(recipientAutocryptStatus);
|
||||
|
||||
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
|
||||
|
||||
assertEquals(CryptoStatusDisplayType.AVAILABLE, status.getDisplayType());
|
||||
assertTrue(status.isProviderStateOk());
|
||||
assertTrue(status.isOpenPgpConfigured());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCurrentCryptoStatus_withOpportunistic__missingKeys() throws Exception {
|
||||
RecipientAutocryptStatus recipientAutocryptStatus = new RecipientAutocryptStatus(
|
||||
RecipientAutocryptStatusType.UNAVAILABLE, null);
|
||||
setupCryptoProvider(recipientAutocryptStatus);
|
||||
|
||||
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
|
||||
|
||||
assertEquals(CryptoStatusDisplayType.UNAVAILABLE, status.getDisplayType());
|
||||
assertTrue(status.isProviderStateOk());
|
||||
assertTrue(status.isOpenPgpConfigured());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCurrentCryptoStatus_withOpportunistic__privateMissingKeys() throws Exception {
|
||||
RecipientAutocryptStatus recipientAutocryptStatus = new RecipientAutocryptStatus(
|
||||
RecipientAutocryptStatusType.UNAVAILABLE, null);
|
||||
setupCryptoProvider(recipientAutocryptStatus);
|
||||
|
||||
recipientPresenter.onCryptoModeChanged(CryptoMode.CHOICE_ENABLED);
|
||||
runBackgroundTask();
|
||||
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
|
||||
|
||||
assertEquals(CryptoStatusDisplayType.ENABLED_ERROR, status.getDisplayType());
|
||||
assertTrue(status.isProviderStateOk());
|
||||
assertTrue(status.isOpenPgpConfigured());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCurrentCryptoStatus_withModeDisabled() throws Exception {
|
||||
RecipientAutocryptStatus recipientAutocryptStatus = new RecipientAutocryptStatus(
|
||||
RecipientAutocryptStatusType.AVAILABLE_UNCONFIRMED, null);
|
||||
setupCryptoProvider(recipientAutocryptStatus);
|
||||
|
||||
recipientPresenter.onCryptoModeChanged(CryptoMode.CHOICE_DISABLED);
|
||||
runBackgroundTask();
|
||||
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
|
||||
|
||||
assertEquals(CryptoStatusDisplayType.AVAILABLE, status.getDisplayType());
|
||||
assertTrue(status.isProviderStateOk());
|
||||
assertTrue(status.isOpenPgpConfigured());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCurrentCryptoStatus_withModePrivate() throws Exception {
|
||||
RecipientAutocryptStatus recipientAutocryptStatus = new RecipientAutocryptStatus(
|
||||
RecipientAutocryptStatusType.AVAILABLE_UNCONFIRMED, null);
|
||||
setupCryptoProvider(recipientAutocryptStatus);
|
||||
|
||||
recipientPresenter.onCryptoModeChanged(CryptoMode.CHOICE_ENABLED);
|
||||
runBackgroundTask();
|
||||
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
|
||||
|
||||
assertEquals(CryptoStatusDisplayType.ENABLED, status.getDisplayType());
|
||||
assertTrue(status.isProviderStateOk());
|
||||
assertTrue(status.isOpenPgpConfigured());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCurrentCryptoStatus_withModeSignOnly() throws Exception {
|
||||
setupCryptoProvider(noRecipientsAutocryptResult);
|
||||
|
||||
recipientPresenter.onMenuSetSignOnly(true);
|
||||
runBackgroundTask();
|
||||
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
|
||||
|
||||
assertEquals(CryptoStatusDisplayType.SIGN_ONLY, status.getDisplayType());
|
||||
assertTrue(status.isProviderStateOk());
|
||||
assertTrue(status.isSigningEnabled());
|
||||
assertTrue(status.isSignOnly());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCurrentCryptoStatus_withModeInline() throws Exception {
|
||||
setupCryptoProvider(noRecipientsAutocryptResult);
|
||||
|
||||
recipientPresenter.onMenuSetPgpInline(true);
|
||||
runBackgroundTask();
|
||||
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
|
||||
|
||||
assertEquals(CryptoStatusDisplayType.UNAVAILABLE, status.getDisplayType());
|
||||
assertTrue(status.isProviderStateOk());
|
||||
assertTrue(status.isPgpInlineModeEnabled());
|
||||
}
|
||||
|
||||
private void runBackgroundTask() {
|
||||
boolean taskRun = Robolectric.getBackgroundThreadScheduler().runOneTask();
|
||||
assertTrue(taskRun);
|
||||
}
|
||||
|
||||
private void setupCryptoProvider(RecipientAutocryptStatus autocryptStatusResult) throws Exception {
|
||||
Account account = mock(Account.class);
|
||||
OpenPgpApi openPgpApi = mock(OpenPgpApi.class);
|
||||
|
||||
when(account.getOpenPgpProvider()).thenReturn(CRYPTO_PROVIDER);
|
||||
when(account.isOpenPgpProviderConfigured()).thenReturn(true);
|
||||
when(account.getOpenPgpKey()).thenReturn(CRYPTO_KEY_ID);
|
||||
recipientPresenter.onSwitchAccount(account);
|
||||
|
||||
when(openPgpApiManager.getOpenPgpProviderState()).thenReturn(OpenPgpProviderState.OK);
|
||||
when(openPgpApiManager.getOpenPgpApi()).thenReturn(openPgpApi);
|
||||
when(autocryptStatusInteractor.retrieveCryptoProviderRecipientStatus(
|
||||
any(OpenPgpApi.class), any(String[].class))).thenReturn(autocryptStatusResult);
|
||||
|
||||
openPgpApiManagerCallback.onOpenPgpProviderStatusChanged();
|
||||
runBackgroundTask();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,307 @@
|
|||
package com.fsck.k9.activity.compose
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.K9RobolectricTest
|
||||
import com.fsck.k9.activity.compose.RecipientMvpView.CryptoSpecialModeDisplayType
|
||||
import com.fsck.k9.activity.compose.RecipientMvpView.CryptoStatusDisplayType
|
||||
import com.fsck.k9.activity.compose.RecipientPresenter.CryptoMode
|
||||
import com.fsck.k9.autocrypt.AutocryptDraftStateHeaderParser
|
||||
import com.fsck.k9.helper.ReplyToParser
|
||||
import com.fsck.k9.helper.ReplyToParser.ReplyToAddresses
|
||||
import com.fsck.k9.mail.Address
|
||||
import com.fsck.k9.mail.Message
|
||||
import com.fsck.k9.mail.Message.RecipientType
|
||||
import com.fsck.k9.message.AutocryptStatusInteractor
|
||||
import com.fsck.k9.message.AutocryptStatusInteractor.RecipientAutocryptStatus
|
||||
import com.fsck.k9.message.AutocryptStatusInteractor.RecipientAutocryptStatusType
|
||||
import com.fsck.k9.message.ComposePgpEnableByDefaultDecider
|
||||
import com.fsck.k9.message.ComposePgpInlineDecider
|
||||
import com.fsck.k9.view.RecipientSelectView.Recipient
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlin.test.assertNotNull
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.koin.test.inject
|
||||
import org.mockito.ArgumentMatchers.eq
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doAnswer
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.stubbing
|
||||
import org.openintents.openpgp.OpenPgpApiManager
|
||||
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpApiManagerCallback
|
||||
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderState
|
||||
import org.openintents.openpgp.util.OpenPgpApi
|
||||
import org.robolectric.Robolectric
|
||||
import org.robolectric.annotation.LooperMode
|
||||
|
||||
private val TO_ADDRESS = Address("to@domain.example")
|
||||
private val CC_ADDRESS = Address("cc@domain.example")
|
||||
private const val CRYPTO_PROVIDER = "crypto_provider"
|
||||
private const val CRYPTO_KEY_ID = 123L
|
||||
|
||||
@LooperMode(LooperMode.Mode.LEGACY)
|
||||
class RecipientPresenterTest : K9RobolectricTest() {
|
||||
private val openPgpApiManager = mock<OpenPgpApiManager> {
|
||||
on { openPgpProviderState } doReturn OpenPgpProviderState.UNCONFIGURED
|
||||
on { setOpenPgpProvider(any(), any()) } doAnswer { invocation ->
|
||||
openPgpApiManagerCallback = invocation.getArgument(1)
|
||||
}
|
||||
}
|
||||
private val recipientMvpView = mock<RecipientMvpView>()
|
||||
private val account = mock<Account>()
|
||||
private val composePgpInlineDecider = mock<ComposePgpInlineDecider>()
|
||||
private val composePgpEnableByDefaultDecider = mock<ComposePgpEnableByDefaultDecider>()
|
||||
private val autocryptStatusInteractor = mock<AutocryptStatusInteractor>()
|
||||
private val replyToParser = mock<ReplyToParser>()
|
||||
private val autocryptDraftStateHeaderParser: AutocryptDraftStateHeaderParser by inject()
|
||||
private lateinit var recipientPresenter: RecipientPresenter
|
||||
|
||||
private val noRecipientsAutocryptResult = RecipientAutocryptStatus(RecipientAutocryptStatusType.NO_RECIPIENTS, null)
|
||||
private var openPgpApiManagerCallback: OpenPgpApiManagerCallback? = null
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
Robolectric.getBackgroundThreadScheduler().pause()
|
||||
|
||||
recipientPresenter = RecipientPresenter(
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
mock(),
|
||||
openPgpApiManager,
|
||||
recipientMvpView,
|
||||
account,
|
||||
composePgpInlineDecider,
|
||||
composePgpEnableByDefaultDecider,
|
||||
autocryptStatusInteractor,
|
||||
replyToParser,
|
||||
autocryptDraftStateHeaderParser
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("It looks like the support version of AsyncTaskLoader handles background tasks differently")
|
||||
fun testInitFromReplyToMessage() {
|
||||
val message = mock<Message>()
|
||||
stubbing(replyToParser) {
|
||||
on { getRecipientsToReplyTo(message, account) } doReturn ReplyToAddresses(arrayOf(TO_ADDRESS))
|
||||
}
|
||||
|
||||
recipientPresenter.initFromReplyToMessage(message, false)
|
||||
runBackgroundTask()
|
||||
|
||||
verify(recipientMvpView).addRecipients(eq(RecipientType.TO), eq(Recipient(TO_ADDRESS)))
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("It looks like the support version of AsyncTaskLoader handles background tasks differently")
|
||||
fun testInitFromReplyToAllMessage() {
|
||||
val message = mock<Message>()
|
||||
val replyToAddresses = ReplyToAddresses(listOf(TO_ADDRESS), listOf(CC_ADDRESS))
|
||||
stubbing(replyToParser) {
|
||||
on { getRecipientsToReplyAllTo(message, account) } doReturn replyToAddresses
|
||||
}
|
||||
|
||||
recipientPresenter.initFromReplyToMessage(message, true)
|
||||
runBackgroundTask()
|
||||
runBackgroundTask()
|
||||
|
||||
verify(recipientMvpView).addRecipients(eq(RecipientType.TO), eq(Recipient(TO_ADDRESS)))
|
||||
verify(recipientMvpView).addRecipients(eq(RecipientType.CC), eq(Recipient(CC_ADDRESS)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initFromReplyToMessage_shouldCallComposePgpInlineDecider() {
|
||||
val message = mock<Message>()
|
||||
stubbing(replyToParser) {
|
||||
on { getRecipientsToReplyTo(message, account) } doReturn ReplyToAddresses(arrayOf(TO_ADDRESS))
|
||||
}
|
||||
|
||||
recipientPresenter.initFromReplyToMessage(message, false)
|
||||
|
||||
verify(composePgpInlineDecider).shouldReplyInline(message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCurrentCryptoStatus_withoutCryptoProvider() {
|
||||
stubbing(openPgpApiManager) {
|
||||
on { openPgpProviderState } doReturn OpenPgpProviderState.UNCONFIGURED
|
||||
}
|
||||
|
||||
recipientPresenter.asyncUpdateCryptoStatus()
|
||||
|
||||
assertNotNull(recipientPresenter.currentCachedCryptoStatus) { status ->
|
||||
assertThat(status.displayType).isEqualTo(CryptoStatusDisplayType.UNCONFIGURED)
|
||||
assertThat(status.specialModeDisplayType).isEqualTo(CryptoSpecialModeDisplayType.NONE)
|
||||
assertThat(status.attachErrorStateOrNull).isNull()
|
||||
assertThat(status.isProviderStateOk()).isFalse()
|
||||
assertThat(status.isOpenPgpConfigured).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCurrentCryptoStatus_withCryptoProvider() {
|
||||
setupCryptoProvider(noRecipientsAutocryptResult)
|
||||
|
||||
assertNotNull(recipientPresenter.currentCachedCryptoStatus) { status ->
|
||||
assertThat(status.displayType).isEqualTo(CryptoStatusDisplayType.UNAVAILABLE)
|
||||
assertThat(status.isProviderStateOk()).isTrue()
|
||||
assertThat(status.isOpenPgpConfigured).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCurrentCryptoStatus_withOpportunistic() {
|
||||
val recipientAutocryptStatus = RecipientAutocryptStatus(
|
||||
RecipientAutocryptStatusType.AVAILABLE_UNCONFIRMED, null
|
||||
)
|
||||
|
||||
setupCryptoProvider(recipientAutocryptStatus)
|
||||
|
||||
assertNotNull(recipientPresenter.currentCachedCryptoStatus) { status ->
|
||||
assertThat(status.displayType).isEqualTo(CryptoStatusDisplayType.AVAILABLE)
|
||||
assertThat(status.isProviderStateOk()).isTrue()
|
||||
assertThat(status.isOpenPgpConfigured).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCurrentCryptoStatus_withOpportunistic__confirmed() {
|
||||
val recipientAutocryptStatus = RecipientAutocryptStatus(
|
||||
RecipientAutocryptStatusType.AVAILABLE_CONFIRMED, null
|
||||
)
|
||||
|
||||
setupCryptoProvider(recipientAutocryptStatus)
|
||||
|
||||
assertNotNull(recipientPresenter.currentCachedCryptoStatus) { status ->
|
||||
assertThat(status.displayType).isEqualTo(CryptoStatusDisplayType.AVAILABLE)
|
||||
assertThat(status.isProviderStateOk()).isTrue()
|
||||
assertThat(status.isOpenPgpConfigured).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCurrentCryptoStatus_withOpportunistic__missingKeys() {
|
||||
val recipientAutocryptStatus = RecipientAutocryptStatus(
|
||||
RecipientAutocryptStatusType.UNAVAILABLE, null
|
||||
)
|
||||
|
||||
setupCryptoProvider(recipientAutocryptStatus)
|
||||
|
||||
assertNotNull(recipientPresenter.currentCachedCryptoStatus) { status ->
|
||||
assertThat(status.displayType).isEqualTo(CryptoStatusDisplayType.UNAVAILABLE)
|
||||
assertThat(status.isProviderStateOk()).isTrue()
|
||||
assertThat(status.isOpenPgpConfigured).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCurrentCryptoStatus_withOpportunistic__privateMissingKeys() {
|
||||
val recipientAutocryptStatus = RecipientAutocryptStatus(
|
||||
RecipientAutocryptStatusType.UNAVAILABLE, null
|
||||
)
|
||||
|
||||
setupCryptoProvider(recipientAutocryptStatus)
|
||||
recipientPresenter.onCryptoModeChanged(CryptoMode.CHOICE_ENABLED)
|
||||
runBackgroundTask()
|
||||
|
||||
assertNotNull(recipientPresenter.currentCachedCryptoStatus) { status ->
|
||||
assertThat(status.displayType).isEqualTo(CryptoStatusDisplayType.ENABLED_ERROR)
|
||||
assertThat(status.isProviderStateOk()).isTrue()
|
||||
assertThat(status.isOpenPgpConfigured).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCurrentCryptoStatus_withModeDisabled() {
|
||||
val recipientAutocryptStatus = RecipientAutocryptStatus(
|
||||
RecipientAutocryptStatusType.AVAILABLE_UNCONFIRMED, null
|
||||
)
|
||||
|
||||
setupCryptoProvider(recipientAutocryptStatus)
|
||||
recipientPresenter.onCryptoModeChanged(CryptoMode.CHOICE_DISABLED)
|
||||
runBackgroundTask()
|
||||
|
||||
assertNotNull(recipientPresenter.currentCachedCryptoStatus) { status ->
|
||||
assertThat(status.displayType).isEqualTo(CryptoStatusDisplayType.AVAILABLE)
|
||||
assertThat(status.isProviderStateOk()).isTrue()
|
||||
assertThat(status.isOpenPgpConfigured).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCurrentCryptoStatus_withModePrivate() {
|
||||
val recipientAutocryptStatus = RecipientAutocryptStatus(
|
||||
RecipientAutocryptStatusType.AVAILABLE_UNCONFIRMED, null
|
||||
)
|
||||
|
||||
setupCryptoProvider(recipientAutocryptStatus)
|
||||
recipientPresenter.onCryptoModeChanged(CryptoMode.CHOICE_ENABLED)
|
||||
runBackgroundTask()
|
||||
|
||||
assertNotNull(recipientPresenter.currentCachedCryptoStatus) { status ->
|
||||
assertThat(status.displayType).isEqualTo(CryptoStatusDisplayType.ENABLED)
|
||||
assertThat(status.isProviderStateOk()).isTrue()
|
||||
assertThat(status.isOpenPgpConfigured).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCurrentCryptoStatus_withModeSignOnly() {
|
||||
setupCryptoProvider(noRecipientsAutocryptResult)
|
||||
|
||||
recipientPresenter.onMenuSetSignOnly(true)
|
||||
runBackgroundTask()
|
||||
|
||||
assertNotNull(recipientPresenter.currentCachedCryptoStatus) { status ->
|
||||
assertThat(status.displayType).isEqualTo(CryptoStatusDisplayType.SIGN_ONLY)
|
||||
assertThat(status.isProviderStateOk()).isTrue()
|
||||
assertThat(status.isOpenPgpConfigured).isTrue()
|
||||
assertThat(status.isSignOnly).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getCurrentCryptoStatus_withModeInline() {
|
||||
setupCryptoProvider(noRecipientsAutocryptResult)
|
||||
|
||||
recipientPresenter.onMenuSetPgpInline(true)
|
||||
runBackgroundTask()
|
||||
|
||||
assertNotNull(recipientPresenter.currentCachedCryptoStatus) { status ->
|
||||
assertThat(status.displayType).isEqualTo(CryptoStatusDisplayType.UNAVAILABLE)
|
||||
assertThat(status.isProviderStateOk()).isTrue()
|
||||
assertThat(status.isPgpInlineModeEnabled).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
private fun runBackgroundTask() {
|
||||
assertThat(Robolectric.getBackgroundThreadScheduler().runOneTask()).isTrue()
|
||||
}
|
||||
|
||||
private fun setupCryptoProvider(autocryptStatusResult: RecipientAutocryptStatus) {
|
||||
stubbing(account) {
|
||||
on { openPgpProvider } doReturn CRYPTO_PROVIDER
|
||||
on { isOpenPgpProviderConfigured } doReturn true
|
||||
on { openPgpKey } doReturn CRYPTO_KEY_ID
|
||||
}
|
||||
|
||||
recipientPresenter.onSwitchAccount(account)
|
||||
|
||||
val openPgpApiMock = mock<OpenPgpApi>()
|
||||
|
||||
stubbing(autocryptStatusInteractor) {
|
||||
on { retrieveCryptoProviderRecipientStatus(eq(openPgpApiMock), any()) } doReturn autocryptStatusResult
|
||||
}
|
||||
|
||||
stubbing(openPgpApiManager) {
|
||||
on { openPgpApi } doReturn openPgpApiMock
|
||||
on { openPgpProviderState } doReturn OpenPgpProviderState.OK
|
||||
}
|
||||
|
||||
openPgpApiManagerCallback!!.onOpenPgpProviderStatusChanged()
|
||||
runBackgroundTask()
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue