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