TrustKeysActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import android.content.Intent;
  4import android.os.Bundle;
  5import android.view.View;
  6import android.view.View.OnClickListener;
  7import android.widget.Button;
  8import android.widget.CompoundButton;
  9import android.widget.LinearLayout;
 10import android.widget.TextView;
 11import android.widget.Toast;
 12
 13import org.whispersystems.libaxolotl.IdentityKey;
 14
 15import java.util.HashMap;
 16import java.util.Map;
 17import java.util.Set;
 18
 19import eu.siacs.conversations.R;
 20import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 21import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
 22import eu.siacs.conversations.entities.Account;
 23import eu.siacs.conversations.entities.Contact;
 24import eu.siacs.conversations.entities.Conversation;
 25import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
 26import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 27import eu.siacs.conversations.xmpp.jid.Jid;
 28
 29public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdated {
 30	private Jid accountJid;
 31	private Jid contactJid;
 32
 33	private boolean hasNoTrustedKeys = true;
 34
 35	private Contact contact;
 36	private Account mAccount;
 37	private TextView keyErrorMessage;
 38	private LinearLayout keyErrorMessageCard;
 39	private TextView ownKeysTitle;
 40	private LinearLayout ownKeys;
 41	private LinearLayout ownKeysCard;
 42	private TextView foreignKeysTitle;
 43	private LinearLayout foreignKeys;
 44	private LinearLayout foreignKeysCard;
 45	private Button mSaveButton;
 46	private Button mCancelButton;
 47
 48	private final Map<String, Boolean> ownKeysToTrust = new HashMap<>();
 49	private final Map<String, Boolean> foreignKeysToTrust = new HashMap<>();
 50
 51	private final OnClickListener mSaveButtonListener = new OnClickListener() {
 52		@Override
 53		public void onClick(View v) {
 54			commitTrusts();
 55			finishOk();
 56		}
 57	};
 58
 59	private final OnClickListener mCancelButtonListener = new OnClickListener() {
 60		@Override
 61		public void onClick(View v) {
 62			setResult(RESULT_CANCELED);
 63			finish();
 64		}
 65	};
 66
 67	@Override
 68	protected void refreshUiReal() {
 69		invalidateOptionsMenu();
 70		populateView();
 71	}
 72
 73	@Override
 74	protected String getShareableUri() {
 75		if (contact != null) {
 76			return contact.getShareableUri();
 77		} else {
 78			return "";
 79		}
 80	}
 81
 82	@Override
 83	protected void onCreate(final Bundle savedInstanceState) {
 84		super.onCreate(savedInstanceState);
 85		setContentView(R.layout.activity_trust_keys);
 86		try {
 87			this.accountJid = Jid.fromString(getIntent().getExtras().getString("account"));
 88		} catch (final InvalidJidException ignored) {
 89		}
 90		try {
 91			this.contactJid = Jid.fromString(getIntent().getExtras().getString("contact"));
 92		} catch (final InvalidJidException ignored) {
 93		}
 94		hasNoTrustedKeys = getIntent().getBooleanExtra("has_no_trusted", false);
 95
 96		keyErrorMessageCard = (LinearLayout) findViewById(R.id.key_error_message_card);
 97		keyErrorMessage = (TextView) findViewById(R.id.key_error_message);
 98		ownKeysTitle = (TextView) findViewById(R.id.own_keys_title);
 99		ownKeys = (LinearLayout) findViewById(R.id.own_keys_details);
100		ownKeysCard = (LinearLayout) findViewById(R.id.own_keys_card);
101		foreignKeysTitle = (TextView) findViewById(R.id.foreign_keys_title);
102		foreignKeys = (LinearLayout) findViewById(R.id.foreign_keys_details);
103		foreignKeysCard = (LinearLayout) findViewById(R.id.foreign_keys_card);
104		mCancelButton = (Button) findViewById(R.id.cancel_button);
105		mCancelButton.setOnClickListener(mCancelButtonListener);
106		mSaveButton = (Button) findViewById(R.id.save_button);
107		mSaveButton.setOnClickListener(mSaveButtonListener);
108
109
110		if (getActionBar() != null) {
111			getActionBar().setHomeButtonEnabled(true);
112			getActionBar().setDisplayHomeAsUpEnabled(true);
113		}
114	}
115
116	private void populateView() {
117		setTitle(getString(R.string.trust_omemo_fingerprints));
118		ownKeys.removeAllViews();
119		foreignKeys.removeAllViews();
120		boolean hasOwnKeys = false;
121		boolean hasForeignKeys = false;
122		for(final String fingerprint : ownKeysToTrust.keySet()) {
123			hasOwnKeys = true;
124			addFingerprintRowWithListeners(ownKeys, contact.getAccount(), fingerprint, false,
125					XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint)), false,
126					new CompoundButton.OnCheckedChangeListener() {
127						@Override
128						public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
129							ownKeysToTrust.put(fingerprint, isChecked);
130							// own fingerprints have no impact on locked status.
131						}
132					},
133					null
134			);
135		}
136		for(final String fingerprint : foreignKeysToTrust.keySet()) {
137			hasForeignKeys = true;
138			addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), fingerprint, false,
139					XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(fingerprint)), false,
140					new CompoundButton.OnCheckedChangeListener() {
141						@Override
142						public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
143							foreignKeysToTrust.put(fingerprint, isChecked);
144							lockOrUnlockAsNeeded();
145						}
146					},
147					null
148			);
149		}
150
151		if(hasOwnKeys) {
152			ownKeysTitle.setText(accountJid.toString());
153			ownKeysCard.setVisibility(View.VISIBLE);
154		}
155		if(hasForeignKeys) {
156			foreignKeysTitle.setText(contactJid.toString());
157			foreignKeysCard.setVisibility(View.VISIBLE);
158		}
159		if(hasPendingKeyFetches()) {
160			setFetching();
161			lock();
162		} else {
163			if (!hasForeignKeys && hasNoOtherTrustedKeys()) {
164				keyErrorMessageCard.setVisibility(View.VISIBLE);
165				keyErrorMessage.setText(R.string.error_no_keys_to_trust);
166				ownKeys.removeAllViews(); ownKeysCard.setVisibility(View.GONE);
167				foreignKeys.removeAllViews(); foreignKeysCard.setVisibility(View.GONE);
168			}
169			lockOrUnlockAsNeeded();
170			setDone();
171		}
172	}
173
174	private boolean reloadFingerprints() {
175		AxolotlService service = this.mAccount.getAxolotlService();
176		Set<IdentityKey> ownKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED);
177		Set<IdentityKey> foreignKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, contact);
178		if (hasNoTrustedKeys) {
179			ownKeysSet.addAll(service.getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED));
180			foreignKeysSet.addAll(service.getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, contact));
181		}
182		for(final IdentityKey identityKey : ownKeysSet) {
183			if(!ownKeysToTrust.containsKey(identityKey)) {
184				ownKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
185			}
186		}
187		for(final IdentityKey identityKey : foreignKeysSet) {
188			if(!foreignKeysToTrust.containsKey(identityKey)) {
189				foreignKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
190			}
191		}
192		return ownKeysSet.size() + foreignKeysSet.size() > 0;
193	}
194
195	@Override
196	public void onBackendConnected() {
197		if ((accountJid != null) && (contactJid != null)) {
198			this.mAccount = xmppConnectionService.findAccountByJid(accountJid);
199			if (this.mAccount == null) {
200				return;
201			}
202			this.contact = this.mAccount.getRoster().getContact(contactJid);
203			ownKeysToTrust.clear();
204			foreignKeysToTrust.clear();
205			reloadFingerprints();
206			populateView();
207		}
208	}
209
210	private boolean hasNoOtherTrustedKeys() {
211		return mAccount == null || mAccount.getAxolotlService().getNumTrustedKeys(contact) == 0;
212	}
213
214	private boolean hasPendingKeyFetches() {
215		return mAccount != null && contact != null && mAccount.getAxolotlService().hasPendingKeyFetches(mAccount,contact);
216	}
217
218
219	@Override
220	public void onKeyStatusUpdated() {
221		boolean keysToTrust = reloadFingerprints();
222		if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) {
223			refreshUi();
224		} else {
225			runOnUiThread(new Runnable() {
226				@Override
227				public void run() {
228					Toast.makeText(TrustKeysActivity.this, "Nothing to do", Toast.LENGTH_SHORT).show();
229					finishOk();
230				}
231			});
232
233		}
234	}
235
236	private void finishOk() {
237		Intent data = new Intent();
238		data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID));
239		setResult(RESULT_OK, data);
240		finish();
241	}
242
243	private void commitTrusts() {
244		for(final String fingerprint :ownKeysToTrust.keySet()) {
245			contact.getAccount().getAxolotlService().setFingerprintTrust(
246					fingerprint,
247					XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint)));
248		}
249		for(final String fingerprint:foreignKeysToTrust.keySet()) {
250			contact.getAccount().getAxolotlService().setFingerprintTrust(
251					fingerprint,
252					XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(fingerprint)));
253		}
254	}
255
256	private void unlock() {
257		mSaveButton.setEnabled(true);
258		mSaveButton.setTextColor(getPrimaryTextColor());
259	}
260
261	private void lock() {
262		mSaveButton.setEnabled(false);
263		mSaveButton.setTextColor(getSecondaryTextColor());
264	}
265
266	private void lockOrUnlockAsNeeded() {
267		if (hasNoOtherTrustedKeys() && !foreignKeysToTrust.values().contains(true)){
268			lock();
269		} else {
270			unlock();
271		}
272	}
273
274	private void setDone() {
275		mSaveButton.setText(getString(R.string.done));
276	}
277
278	private void setFetching() {
279		mSaveButton.setText(getString(R.string.fetching_keys));
280	}
281}