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