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