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					null
131			);
132		}
133		for(final String fingerprint : foreignKeysToTrust.keySet()) {
134			hasForeignKeys = true;
135			addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), fingerprint, false,
136					XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(fingerprint)), false,
137					new CompoundButton.OnCheckedChangeListener() {
138						@Override
139						public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
140							foreignKeysToTrust.put(fingerprint, isChecked);
141							lockOrUnlockAsNeeded();
142						}
143					},
144					null,
145					null
146			);
147		}
148
149		if(hasOwnKeys) {
150			ownKeysTitle.setText(accountJid.toString());
151			ownKeysCard.setVisibility(View.VISIBLE);
152		}
153		if(hasForeignKeys) {
154			foreignKeysTitle.setText(contactJid.toString());
155			foreignKeysCard.setVisibility(View.VISIBLE);
156		}
157		if(hasPendingKeyFetches()) {
158			setFetching();
159			lock();
160		} else {
161			if (!hasForeignKeys && hasNoOtherTrustedKeys()) {
162				keyErrorMessageCard.setVisibility(View.VISIBLE);
163				keyErrorMessage.setText(R.string.error_no_keys_to_trust);
164				ownKeys.removeAllViews(); ownKeysCard.setVisibility(View.GONE);
165				foreignKeys.removeAllViews(); foreignKeysCard.setVisibility(View.GONE);
166			}
167			lockOrUnlockAsNeeded();
168			setDone();
169		}
170	}
171
172	private boolean reloadFingerprints() {
173		ownKeysToTrust.clear();
174		foreignKeysToTrust.clear();
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 (hasNoOtherTrustedKeys() && ownKeysSet.size() == 0) {
179			foreignKeysSet.addAll(service.getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, contact));
180		}
181		for(final IdentityKey identityKey : ownKeysSet) {
182			if(!ownKeysToTrust.containsKey(identityKey)) {
183				ownKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
184			}
185		}
186		for(final IdentityKey identityKey : foreignKeysSet) {
187			if(!foreignKeysToTrust.containsKey(identityKey)) {
188				foreignKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
189			}
190		}
191		return ownKeysSet.size() + foreignKeysSet.size() > 0;
192	}
193
194	@Override
195	public void onBackendConnected() {
196		if ((accountJid != null) && (contactJid != null)) {
197			this.mAccount = xmppConnectionService.findAccountByJid(accountJid);
198			if (this.mAccount == null) {
199				return;
200			}
201			this.contact = this.mAccount.getRoster().getContact(contactJid);
202			reloadFingerprints();
203			populateView();
204		}
205	}
206
207	private boolean hasNoOtherTrustedKeys() {
208		return mAccount == null || mAccount.getAxolotlService().getNumTrustedKeys(contact) == 0;
209	}
210
211	private boolean hasPendingKeyFetches() {
212		return mAccount != null && contact != null && mAccount.getAxolotlService().hasPendingKeyFetches(mAccount,contact);
213	}
214
215
216	@Override
217	public void onKeyStatusUpdated(final AxolotlService.FetchStatus report) {
218		if (report != null) {
219			runOnUiThread(new Runnable() {
220				@Override
221				public void run() {
222					switch (report) {
223						case ERROR:
224							Toast.makeText(TrustKeysActivity.this,R.string.error_fetching_omemo_key,Toast.LENGTH_SHORT).show();
225							break;
226						case SUCCESS_VERIFIED:
227							Toast.makeText(TrustKeysActivity.this,R.string.verified_omemo_key_with_certificate,Toast.LENGTH_LONG).show();
228							break;
229					}
230				}
231			});
232
233		}
234		boolean keysToTrust = reloadFingerprints();
235		if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) {
236			refreshUi();
237		} else {
238			runOnUiThread(new Runnable() {
239				@Override
240				public void run() {
241					finishOk();
242				}
243			});
244
245		}
246	}
247
248	private void finishOk() {
249		Intent data = new Intent();
250		data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID));
251		setResult(RESULT_OK, data);
252		finish();
253	}
254
255	private void commitTrusts() {
256		for(final String fingerprint :ownKeysToTrust.keySet()) {
257			contact.getAccount().getAxolotlService().setFingerprintTrust(
258					fingerprint,
259					XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint)));
260		}
261		for(final String fingerprint:foreignKeysToTrust.keySet()) {
262			contact.getAccount().getAxolotlService().setFingerprintTrust(
263					fingerprint,
264					XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(fingerprint)));
265		}
266	}
267
268	private void unlock() {
269		mSaveButton.setEnabled(true);
270		mSaveButton.setTextColor(getPrimaryTextColor());
271	}
272
273	private void lock() {
274		mSaveButton.setEnabled(false);
275		mSaveButton.setTextColor(getSecondaryTextColor());
276	}
277
278	private void lockOrUnlockAsNeeded() {
279		if (hasNoOtherTrustedKeys() && !foreignKeysToTrust.values().contains(true)){
280			lock();
281		} else {
282			unlock();
283		}
284	}
285
286	private void setDone() {
287		mSaveButton.setText(getString(R.string.done));
288	}
289
290	private void setFetching() {
291		mSaveButton.setText(getString(R.string.fetching_keys));
292	}
293}