diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index a07a58a5b..21737c46d 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -77,6 +77,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { private final Map> deviceIds; private final Map messageCache; private final FetchStatusMap fetchStatusMap; + private final Map fetchDeviceListStatus = new HashMap<>(); private final HashMap> fetchDeviceIdsMap = new HashMap<>(); private final SerialSingleThreadExecutor executor; private int numPublishTriesOnEmptyPep = 0; @@ -97,6 +98,20 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } } + private boolean hasErrorFetchingDeviceList(Jid jid) { + Boolean status = fetchDeviceListStatus.get(jid); + return status != null && !status; + } + + public boolean hasErrorFetchingDeviceList(List jids) { + for(Jid jid : jids) { + if (hasErrorFetchingDeviceList(jid)) { + return true; + } + } + return false; + } + public boolean fetchMapHasErrors(List jids) { for (Jid jid : jids) { if (deviceIds.get(jid) != null) { @@ -361,6 +376,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { public void clearErrorsInFetchStatusMap(Jid jid) { fetchStatusMap.clearErrorFor(jid); + fetchDeviceListStatus.remove(jid); } public void regenerateKeys(boolean wipeOther) { @@ -368,6 +384,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { sessions.clear(); fetchStatusMap.clear(); fetchDeviceIdsMap.clear(); + fetchDeviceListStatus.clear(); publishBundlesIfNeeded(true, wipeOther); } @@ -960,26 +977,29 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { this.fetchDeviceIdsMap.put(jid, callbacks); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": fetching device ids for " + jid); IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(jid); - mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - synchronized (fetchDeviceIdsMap) { - List callbacks = fetchDeviceIdsMap.remove(jid); - if (packet.getType() == IqPacket.TYPE.RESULT) { - Element item = mXmppConnectionService.getIqParser().getItem(packet); - Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); - registerDevices(jid, deviceIds); - if (callbacks != null) { - for (OnDeviceIdsFetched callback : callbacks) { - callback.fetched(jid, deviceIds); - } + mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> { + synchronized (fetchDeviceIdsMap) { + List callbacks1 = fetchDeviceIdsMap.remove(jid); + if (response.getType() == IqPacket.TYPE.RESULT) { + fetchDeviceListStatus.put(jid, true); + Element item = mXmppConnectionService.getIqParser().getItem(response); + Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + registerDevices(jid, deviceIds); + if (callbacks1 != null) { + for (OnDeviceIdsFetched callback1 : callbacks1) { + callback1.fetched(jid, deviceIds); } + } + } else { + if (response.getType() == IqPacket.TYPE.TIMEOUT) { + fetchDeviceListStatus.remove(jid); } else { - Log.d(Config.LOGTAG, packet.toString()); - if (callbacks != null) { - for (OnDeviceIdsFetched callback : callbacks) { - callback.fetched(jid, null); - } + fetchDeviceListStatus.put(jid, false); + } + Log.d(Config.LOGTAG, response.toString()); + if (callbacks1 != null) { + for (OnDeviceIdsFetched callback1 : callbacks1) { + callback1.fetched(jid, null); } } } @@ -993,14 +1013,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { final ArrayList unfinishedJids = new ArrayList<>(jids); synchronized (unfinishedJids) { for (Jid jid : unfinishedJids) { - fetchDeviceIds(jid, new OnDeviceIdsFetched() { - @Override - public void fetched(Jid jid, Set deviceIds) { - synchronized (unfinishedJids) { - unfinishedJids.remove(jid); - if (unfinishedJids.size() == 0 && callback != null) { - callback.fetched(); - } + fetchDeviceIds(jid, (j, deviceIds) -> { + synchronized (unfinishedJids) { + unfinishedJids.remove(j); + if (unfinishedJids.size() == 0 && callback != null) { + callback.fetched(); } } }); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 13f57d7d9..7f5dfd68e 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -653,11 +653,11 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke break; case Message.ENCRYPTION_AXOLOTL: if (!trustKeysIfNeeded(REQUEST_TRUST_KEYS_TEXT)) { - sendAxolotlMessage(message); + sendMessage(message); } break; default: - sendPlainTextMessage(message); + sendMessage(message); } } @@ -724,7 +724,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke case REQUEST_TRUST_KEYS_TEXT: final String body = this.binding.textinput.getText().toString(); Message message = new Message(conversation, body, conversation.getNextEncryption()); - sendAxolotlMessage(message); + sendMessage(message); break; case REQUEST_TRUST_KEYS_MENU: int choice = data.getIntExtra("choice", ATTACHMENT_CHOICE_INVALID); @@ -2222,7 +2222,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke this.binding.snackbar.setVisibility(View.GONE); } - protected void sendPlainTextMessage(Message message) { + protected void sendMessage(Message message) { activity.xmppConnectionService.sendMessage(message); messageSent(); } @@ -2339,11 +2339,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke builder.create().show(); } - protected void sendAxolotlMessage(final Message message) { - activity.xmppConnectionService.sendMessage(message); - messageSent(); - } - public void appendText(String text) { if (text == null) { return; diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index 0c2e06d7c..f3ef8983b 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.ui; +import android.app.AlertDialog; import android.content.Intent; import android.databinding.DataBindingUtil; import android.os.Bundle; @@ -10,7 +11,6 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; -import android.widget.CompoundButton; import android.widget.Toast; import org.whispersystems.libsignal.IdentityKey; @@ -29,7 +29,9 @@ import eu.siacs.conversations.crypto.axolotl.FingerprintStatus; import eu.siacs.conversations.databinding.ActivityTrustKeysBinding; import eu.siacs.conversations.databinding.KeysCardBinding; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.IrregularUnicodeDetector; import eu.siacs.conversations.utils.XmppUri; @@ -38,33 +40,21 @@ import rocks.xmpp.addr.Jid; public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdated { + private final Map ownKeysToTrust = new HashMap<>(); + private final Map> foreignKeysToTrust = new HashMap<>(); + private final OnClickListener mCancelButtonListener = v -> { + setResult(RESULT_CANCELED); + finish(); + }; private List contactJids; - private Account mAccount; private Conversation mConversation; - + private final OnClickListener mSaveButtonListener = v -> { + commitTrusts(); + finishOk(false); + }; private AtomicBoolean mUseCameraHintShown = new AtomicBoolean(false); - private AxolotlService.FetchStatus lastFetchReport = AxolotlService.FetchStatus.SUCCESS; - - private final Map ownKeysToTrust = new HashMap<>(); - private final Map> foreignKeysToTrust = new HashMap<>(); - - private final OnClickListener mSaveButtonListener = new OnClickListener() { - @Override - public void onClick(View v) { - commitTrusts(); - finishOk(); - } - }; - - private final OnClickListener mCancelButtonListener = new OnClickListener() { - @Override - public void onClick(View v) { - setResult(RESULT_CANCELED); - finish(); - } - }; private Toast mUseCameraHintToast = null; private ActivityTrustKeysBinding binding; @@ -77,9 +67,9 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - this.binding = DataBindingUtil.setContentView(this,R.layout.activity_trust_keys); + this.binding = DataBindingUtil.setContentView(this, R.layout.activity_trust_keys); this.contactJids = new ArrayList<>(); - for(String jid : getIntent().getStringArrayExtra("contacts")) { + for (String jid : getIntent().getStringArrayExtra("contacts")) { try { this.contactJids.add(Jid.of(jid)); } catch (IllegalArgumentException e) { @@ -97,7 +87,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat } if (savedInstanceState != null) { - mUseCameraHintShown.set(savedInstanceState.getBoolean("camera_hint_shown",false)); + mUseCameraHintShown.set(savedInstanceState.getBoolean("camera_hint_shown", false)); } } @@ -116,9 +106,9 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat } private void showCameraToast() { - mUseCameraHintToast = Toast.makeText(this,R.string.use_camera_icon_to_scan_barcode,Toast.LENGTH_LONG); + mUseCameraHintToast = Toast.makeText(this, R.string.use_camera_icon_to_scan_barcode, Toast.LENGTH_LONG); ActionBar actionBar = getSupportActionBar(); - mUseCameraHintToast.setGravity(Gravity.TOP | Gravity.END, 0 ,actionBar == null ? 0 : actionBar.getHeight()); + mUseCameraHintToast.setGravity(Gravity.TOP | Gravity.END, 0, actionBar == null ? 0 : actionBar.getHeight()); mUseCameraHintToast.show(); } @@ -151,19 +141,19 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat && mAccount != null && uri.hasFingerprints() && mAccount.getAxolotlService().getCryptoTargets(mConversation).contains(uri.getJid())) { - boolean performedVerification = xmppConnectionService.verifyFingerprints(mAccount.getRoster().getContact(uri.getJid()),uri.getFingerprints()); + boolean performedVerification = xmppConnectionService.verifyFingerprints(mAccount.getRoster().getContact(uri.getJid()), uri.getFingerprints()); boolean keys = reloadFingerprints(); if (performedVerification && !keys && !hasNoOtherTrustedKeys() && !hasPendingKeyFetches()) { - Toast.makeText(this,R.string.all_omemo_keys_have_been_verified, Toast.LENGTH_SHORT).show(); - finishOk(); + Toast.makeText(this, R.string.all_omemo_keys_have_been_verified, Toast.LENGTH_SHORT).show(); + finishOk(false); return; } else if (performedVerification) { - Toast.makeText(this,R.string.verified_fingerprints,Toast.LENGTH_SHORT).show(); + Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show(); } } else { reloadFingerprints(); - Log.d(Config.LOGTAG,"xmpp uri was: "+uri.getJid()+" has Fingerprints: "+Boolean.toString(uri.hasFingerprints())); - Toast.makeText(this,R.string.barcode_does_not_contain_fingerprints_for_this_conversation,Toast.LENGTH_SHORT).show(); + Log.d(Config.LOGTAG, "xmpp uri was: " + uri.getJid() + " has Fingerprints: " + Boolean.toString(uri.hasFingerprints())); + Toast.makeText(this, R.string.barcode_does_not_contain_fingerprints_for_this_conversation, Toast.LENGTH_SHORT).show(); } populateView(); } @@ -174,16 +164,13 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat binding.foreignKeys.removeAllViews(); boolean hasOwnKeys = false; boolean hasForeignKeys = false; - for(final String fingerprint : ownKeysToTrust.keySet()) { + for (final String fingerprint : ownKeysToTrust.keySet()) { hasOwnKeys = true; addFingerprintRowWithListeners(binding.ownKeysDetails, mAccount, fingerprint, false, FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint)), false, false, - new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - ownKeysToTrust.put(fingerprint, isChecked); - // own fingerprints have no impact on locked status. - } + (buttonView, isChecked) -> { + ownKeysToTrust.put(fingerprint, isChecked); + // own fingerprints have no impact on locked status. } ); } @@ -191,9 +178,9 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat synchronized (this.foreignKeysToTrust) { for (Map.Entry> entry : foreignKeysToTrust.entrySet()) { hasForeignKeys = true; - KeysCardBinding keysCardBinding = DataBindingUtil.inflate(getLayoutInflater(),R.layout.keys_card, binding.foreignKeys,false); + KeysCardBinding keysCardBinding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.keys_card, binding.foreignKeys, false); final Jid jid = entry.getKey(); - keysCardBinding.foreignKeysTitle.setText(IrregularUnicodeDetector.style(this,jid)); + keysCardBinding.foreignKeysTitle.setText(IrregularUnicodeDetector.style(this, jid)); keysCardBinding.foreignKeysTitle.setOnClickListener(v -> switchToContactDetails(mAccount.getRoster().getContact(jid))); final Map fingerprints = entry.getValue(); for (final String fingerprint : fingerprints.keySet()) { @@ -223,30 +210,37 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat } } - if ((hasOwnKeys || foreignActuallyHasKeys()) && isCameraFeatureAvailable() && mUseCameraHintShown.compareAndSet(false,true)) { + if ((hasOwnKeys || foreignActuallyHasKeys()) && isCameraFeatureAvailable() && mUseCameraHintShown.compareAndSet(false, true)) { showCameraToast(); } binding.ownKeysTitle.setText(mAccount.getJid().asBareJid().toString()); binding.ownKeysCard.setVisibility(hasOwnKeys ? View.VISIBLE : View.GONE); binding.foreignKeys.setVisibility(hasForeignKeys ? View.VISIBLE : View.GONE); - if(hasPendingKeyFetches()) { + if (hasPendingKeyFetches()) { setFetching(); lock(); } else { if (!hasForeignKeys && hasNoOtherTrustedKeys()) { binding.keyErrorMessageCard.setVisibility(View.VISIBLE); - if (lastFetchReport == AxolotlService.FetchStatus.ERROR - || mAccount.getAxolotlService().fetchMapHasErrors(contactJids)) { - if (anyWithoutMutualPresenceSubscription(contactJids)) { - binding.keyErrorMessage.setText(R.string.error_no_keys_to_trust_presence); - } else { - binding.keyErrorMessage.setText(R.string.error_no_keys_to_trust_server_error); - } + boolean lastReportWasError = lastFetchReport == AxolotlService.FetchStatus.ERROR; + boolean errorFetchingBundle = mAccount.getAxolotlService().fetchMapHasErrors(contactJids); + boolean errorFetchingDeviceList = mAccount.getAxolotlService().hasErrorFetchingDeviceList(contactJids); + boolean anyWithoutMutualPresenceSubscription = anyWithoutMutualPresenceSubscription(contactJids); + if (errorFetchingDeviceList) { + binding.keyErrorMessage.setVisibility(View.VISIBLE); + binding.keyErrorMessage.setText(R.string.error_trustkey_device_list); + } else if (errorFetchingBundle || lastReportWasError) { + binding.keyErrorMessage.setVisibility(View.VISIBLE); + binding.keyErrorMessage.setText(R.string.error_trustkey_bundle); } else { - binding.keyErrorMessage.setText(R.string.error_no_keys_to_trust); + binding.keyErrorMessage.setVisibility(View.GONE); } + this.binding.keyErrorHintMutual.setVisibility(anyWithoutMutualPresenceSubscription ? View.VISIBLE : View.GONE); + Contact contact = mAccount.getRoster().getContact(contactJids.get(0)); + binding.keyErrorGeneral.setText(getString(R.string.error_trustkey_general, contact.getDisplayName())); binding.ownKeysDetails.removeAllViews(); + binding.disableButton.setOnClickListener(this::disableEncryptionDialog); binding.ownKeysCard.setVisibility(View.GONE); binding.foreignKeys.removeAllViews(); binding.foreignKeys.setVisibility(View.GONE); @@ -256,8 +250,21 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat } } - private boolean anyWithoutMutualPresenceSubscription(List contactJids){ - for(Jid jid : contactJids) { + private void disableEncryptionDialog(View view) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.disable_encryption); + builder.setMessage(R.string.disable_encryption_message); + builder.setPositiveButton(R.string.disable_now, (dialog, which) -> { + mConversation.setNextEncryption(Message.ENCRYPTION_NONE); + xmppConnectionService.updateConversation(mConversation); + finishOk(true); + }); + builder.setNegativeButton(R.string.cancel, null); + builder.create().show(); + } + + private boolean anyWithoutMutualPresenceSubscription(List contactJids) { + for (Jid jid : contactJids) { if (!mAccount.getRoster().getContact(jid).mutualPresenceSubscription()) { return true; } @@ -277,13 +284,13 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat } private boolean reloadFingerprints() { - List acceptedTargets = mConversation == null ? new ArrayList() : mConversation.getAcceptedCryptoTargets(); + List acceptedTargets = mConversation == null ? new ArrayList<>() : mConversation.getAcceptedCryptoTargets(); ownKeysToTrust.clear(); AxolotlService service = this.mAccount.getAxolotlService(); Set ownKeysSet = service.getKeysWithTrust(FingerprintStatus.createActiveUndecided()); - for(final IdentityKey identityKey : ownKeysSet) { + for (final IdentityKey identityKey : ownKeysSet) { final String fingerprint = CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()); - if(!ownKeysToTrust.containsKey(fingerprint)) { + if (!ownKeysToTrust.containsKey(fingerprint)) { ownKeysToTrust.put(fingerprint, false); } } @@ -344,25 +351,22 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat final boolean keysToTrust = reloadFingerprints(); if (report != null) { lastFetchReport = report; - runOnUiThread(new Runnable() { - @Override - public void run() { - if (mUseCameraHintToast != null && !keysToTrust) { - mUseCameraHintToast.cancel(); - } - switch (report) { - case ERROR: - Toast.makeText(TrustKeysActivity.this,R.string.error_fetching_omemo_key,Toast.LENGTH_SHORT).show(); - break; - case SUCCESS_TRUSTED: - Toast.makeText(TrustKeysActivity.this,R.string.blindly_trusted_omemo_keys,Toast.LENGTH_LONG).show(); - break; - case SUCCESS_VERIFIED: - Toast.makeText(TrustKeysActivity.this, - Config.X509_VERIFICATION ? R.string.verified_omemo_key_with_certificate : R.string.all_omemo_keys_have_been_verified, - Toast.LENGTH_LONG).show(); - break; - } + runOnUiThread(() -> { + if (mUseCameraHintToast != null && !keysToTrust) { + mUseCameraHintToast.cancel(); + } + switch (report) { + case ERROR: + Toast.makeText(TrustKeysActivity.this, R.string.error_fetching_omemo_key, Toast.LENGTH_SHORT).show(); + break; + case SUCCESS_TRUSTED: + Toast.makeText(TrustKeysActivity.this, R.string.blindly_trusted_omemo_keys, Toast.LENGTH_LONG).show(); + break; + case SUCCESS_VERIFIED: + Toast.makeText(TrustKeysActivity.this, + Config.X509_VERIFICATION ? R.string.verified_omemo_key_with_certificate : R.string.all_omemo_keys_have_been_verified, + Toast.LENGTH_LONG).show(); + break; } }); @@ -370,25 +374,21 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) { refreshUi(); } else { - runOnUiThread(new Runnable() { - @Override - public void run() { - finishOk(); - } - }); + runOnUiThread(() -> finishOk(false)); } } - private void finishOk() { + private void finishOk(boolean disabled) { Intent data = new Intent(); data.putExtra("choice", getIntent().getIntExtra("choice", ConversationFragment.ATTACHMENT_CHOICE_INVALID)); + data.putExtra("disabled", disabled); setResult(RESULT_OK, data); finish(); } private void commitTrusts() { - for(final String fingerprint :ownKeysToTrust.keySet()) { + for (final String fingerprint : ownKeysToTrust.keySet()) { mAccount.getAxolotlService().setFingerprintTrust( fingerprint, FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint))); diff --git a/src/main/res/layout/activity_trust_keys.xml b/src/main/res/layout/activity_trust_keys.xml index 88d5066fd..616621776 100644 --- a/src/main/res/layout/activity_trust_keys.xml +++ b/src/main/res/layout/activity_trust_keys.xml @@ -27,6 +27,10 @@ android:layout_marginTop="@dimen/activity_vertical_margin" android:visibility="gone"> + + android:textAppearance="@style/TextAppearance.AppCompat.Title" + android:layout_marginBottom="8sp"/> + android:textAppearance="@style/TextAppearance.AppCompat.Subhead"/> + + + + + +