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.ArrayList;
 16import java.util.HashMap;
 17import java.util.List;
 18import java.util.Map;
 19import java.util.Set;
 20
 21import eu.siacs.conversations.OmemoActivity;
 22import eu.siacs.conversations.R;
 23import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 24import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
 25import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
 26import eu.siacs.conversations.entities.Account;
 27import eu.siacs.conversations.entities.Conversation;
 28import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
 29import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 30import eu.siacs.conversations.xmpp.jid.Jid;
 31
 32public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdated {
 33	private List<Jid> contactJids;
 34
 35	private Account mAccount;
 36	private Conversation mConversation;
 37	private TextView keyErrorMessage;
 38	private LinearLayout keyErrorMessageCard;
 39	private TextView ownKeysTitle;
 40	private LinearLayout ownKeys;
 41	private LinearLayout ownKeysCard;
 42	private LinearLayout foreignKeys;
 43	private Button mSaveButton;
 44	private Button mCancelButton;
 45
 46	private AxolotlService.FetchStatus lastFetchReport = AxolotlService.FetchStatus.SUCCESS;
 47
 48	private final Map<String, Boolean> ownKeysToTrust = new HashMap<>();
 49	private final Map<Jid,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 void onCreate(final Bundle savedInstanceState) {
 75		super.onCreate(savedInstanceState);
 76		setContentView(R.layout.activity_trust_keys);
 77		this.contactJids = new ArrayList<>();
 78		for(String jid : getIntent().getStringArrayExtra("contacts")) {
 79			try {
 80				this.contactJids.add(Jid.fromString(jid));
 81			} catch (InvalidJidException e) {
 82				e.printStackTrace();
 83			}
 84		}
 85
 86		keyErrorMessageCard = (LinearLayout) findViewById(R.id.key_error_message_card);
 87		keyErrorMessage = (TextView) findViewById(R.id.key_error_message);
 88		ownKeysTitle = (TextView) findViewById(R.id.own_keys_title);
 89		ownKeys = (LinearLayout) findViewById(R.id.own_keys_details);
 90		ownKeysCard = (LinearLayout) findViewById(R.id.own_keys_card);
 91		foreignKeys = (LinearLayout) findViewById(R.id.foreign_keys);
 92		mCancelButton = (Button) findViewById(R.id.cancel_button);
 93		mCancelButton.setOnClickListener(mCancelButtonListener);
 94		mSaveButton = (Button) findViewById(R.id.save_button);
 95		mSaveButton.setOnClickListener(mSaveButtonListener);
 96
 97
 98		if (getActionBar() != null) {
 99			getActionBar().setHomeButtonEnabled(true);
100			getActionBar().setDisplayHomeAsUpEnabled(true);
101		}
102	}
103
104	private void populateView() {
105		setTitle(getString(R.string.trust_omemo_fingerprints));
106		ownKeys.removeAllViews();
107		foreignKeys.removeAllViews();
108		boolean hasOwnKeys = false;
109		boolean hasForeignKeys = false;
110		for(final String fingerprint : ownKeysToTrust.keySet()) {
111			hasOwnKeys = true;
112			addFingerprintRowWithListeners(ownKeys, mAccount, fingerprint, false,
113					FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint)), false, false,
114					new CompoundButton.OnCheckedChangeListener() {
115						@Override
116						public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
117							ownKeysToTrust.put(fingerprint, isChecked);
118							// own fingerprints have no impact on locked status.
119						}
120					}
121			);
122		}
123
124		synchronized (this.foreignKeysToTrust) {
125			for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) {
126				hasForeignKeys = true;
127				final LinearLayout layout = (LinearLayout) getLayoutInflater().inflate(R.layout.keys_card, foreignKeys, false);
128				final Jid jid = entry.getKey();
129				final TextView header = (TextView) layout.findViewById(R.id.foreign_keys_title);
130				final LinearLayout keysContainer = (LinearLayout) layout.findViewById(R.id.foreign_keys_details);
131				final TextView informNoKeys = (TextView) layout.findViewById(R.id.no_keys_to_accept);
132				header.setText(jid.toString());
133				final Map<String, Boolean> fingerprints = entry.getValue();
134				for (final String fingerprint : fingerprints.keySet()) {
135					addFingerprintRowWithListeners(keysContainer, mAccount, fingerprint, false,
136							FingerprintStatus.createActive(fingerprints.get(fingerprint)), false, false,
137							new CompoundButton.OnCheckedChangeListener() {
138								@Override
139								public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
140									fingerprints.put(fingerprint, isChecked);
141									lockOrUnlockAsNeeded();
142								}
143							}
144					);
145				}
146				if (fingerprints.size() == 0) {
147					informNoKeys.setVisibility(View.VISIBLE);
148					informNoKeys.setText(getString(R.string.no_keys_just_confirm,mAccount.getRoster().getContact(jid).getDisplayName()));
149				} else {
150					informNoKeys.setVisibility(View.GONE);
151				}
152				foreignKeys.addView(layout);
153			}
154		}
155
156		ownKeysTitle.setText(mAccount.getJid().toBareJid().toString());
157		ownKeysCard.setVisibility(hasOwnKeys ? View.VISIBLE : View.GONE);
158		foreignKeys.setVisibility(hasForeignKeys ? View.VISIBLE : View.GONE);
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						|| mAccount.getAxolotlService().fetchMapHasErrors(contactJids)) {
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();
172				ownKeysCard.setVisibility(View.GONE);
173				foreignKeys.removeAllViews();
174				foreignKeys.setVisibility(View.GONE);
175			}
176			lockOrUnlockAsNeeded();
177			setDone();
178		}
179	}
180
181	private boolean reloadFingerprints() {
182		List<Jid> acceptedTargets = mConversation == null ? new ArrayList<Jid>() : mConversation.getAcceptedCryptoTargets();
183		ownKeysToTrust.clear();
184		AxolotlService service = this.mAccount.getAxolotlService();
185		Set<IdentityKey> ownKeysSet = service.getKeysWithTrust(FingerprintStatus.createActiveUndecided());
186		for(final IdentityKey identityKey : ownKeysSet) {
187			if(!ownKeysToTrust.containsKey(identityKey)) {
188				ownKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
189			}
190		}
191		synchronized (this.foreignKeysToTrust) {
192			foreignKeysToTrust.clear();
193			for (Jid jid : contactJids) {
194				Set<IdentityKey> foreignKeysSet = service.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), jid);
195				if (hasNoOtherTrustedKeys(jid) && ownKeysSet.size() == 0) {
196					foreignKeysSet.addAll(service.getKeysWithTrust(FingerprintStatus.createActive(false), jid));
197				}
198				Map<String, Boolean> foreignFingerprints = new HashMap<>();
199				for (final IdentityKey identityKey : foreignKeysSet) {
200					if (!foreignFingerprints.containsKey(identityKey)) {
201						foreignFingerprints.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
202					}
203				}
204				if (foreignFingerprints.size() > 0 || !acceptedTargets.contains(jid)) {
205					foreignKeysToTrust.put(jid, foreignFingerprints);
206				}
207			}
208		}
209		return ownKeysSet.size() + foreignKeysToTrust.size() > 0;
210	}
211
212	public void onBackendConnected() {
213		Intent intent = getIntent();
214		this.mAccount = extractAccount(intent);
215		if (this.mAccount != null && intent != null) {
216			String uuid = intent.getStringExtra("conversation");
217			this.mConversation = xmppConnectionService.findConversationByUuid(uuid);
218			reloadFingerprints();
219			populateView();
220		}
221	}
222
223	private boolean hasNoOtherTrustedKeys() {
224		return mAccount == null || mAccount.getAxolotlService().anyTargetHasNoTrustedKeys(contactJids);
225	}
226
227	private boolean hasNoOtherTrustedKeys(Jid contact) {
228		return mAccount == null || mAccount.getAxolotlService().getNumTrustedKeys(contact) == 0;
229	}
230
231	private boolean hasPendingKeyFetches() {
232		return mAccount != null && mAccount.getAxolotlService().hasPendingKeyFetches(mAccount, contactJids);
233	}
234
235
236	@Override
237	public void onKeyStatusUpdated(final AxolotlService.FetchStatus report) {
238		if (report != null) {
239			lastFetchReport = report;
240			runOnUiThread(new Runnable() {
241				@Override
242				public void run() {
243					switch (report) {
244						case ERROR:
245							Toast.makeText(TrustKeysActivity.this,R.string.error_fetching_omemo_key,Toast.LENGTH_SHORT).show();
246							break;
247						case SUCCESS_VERIFIED:
248							Toast.makeText(TrustKeysActivity.this,R.string.verified_omemo_key_with_certificate,Toast.LENGTH_LONG).show();
249							break;
250					}
251				}
252			});
253
254		}
255		boolean keysToTrust = reloadFingerprints();
256		if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) {
257			refreshUi();
258		} else {
259			runOnUiThread(new Runnable() {
260				@Override
261				public void run() {
262					finishOk();
263				}
264			});
265
266		}
267	}
268
269	private void finishOk() {
270		Intent data = new Intent();
271		data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID));
272		setResult(RESULT_OK, data);
273		finish();
274	}
275
276	private void commitTrusts() {
277		for(final String fingerprint :ownKeysToTrust.keySet()) {
278			mAccount.getAxolotlService().setFingerprintTrust(
279					fingerprint,
280					FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint)));
281		}
282		List<Jid> acceptedTargets = mConversation == null ? new ArrayList<Jid>() : mConversation.getAcceptedCryptoTargets();
283		synchronized (this.foreignKeysToTrust) {
284			for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) {
285				Jid jid = entry.getKey();
286				Map<String, Boolean> value = entry.getValue();
287				if (!acceptedTargets.contains(jid)) {
288					acceptedTargets.add(jid);
289				}
290				for (final String fingerprint : value.keySet()) {
291					mAccount.getAxolotlService().setFingerprintTrust(
292							fingerprint,
293							FingerprintStatus.createActive(value.get(fingerprint)));
294				}
295			}
296		}
297		if (mConversation != null && mConversation.getMode() == Conversation.MODE_MULTI) {
298			mConversation.setAcceptedCryptoTargets(acceptedTargets);
299			xmppConnectionService.updateConversation(mConversation);
300		}
301	}
302
303	private void unlock() {
304		mSaveButton.setEnabled(true);
305		mSaveButton.setTextColor(getPrimaryTextColor());
306	}
307
308	private void lock() {
309		mSaveButton.setEnabled(false);
310		mSaveButton.setTextColor(getSecondaryTextColor());
311	}
312
313	private void lockOrUnlockAsNeeded() {
314		synchronized (this.foreignKeysToTrust) {
315			for (Jid jid : contactJids) {
316				Map<String, Boolean> fingerprints = foreignKeysToTrust.get(jid);
317				if (hasNoOtherTrustedKeys(jid) && (fingerprints == null || !fingerprints.values().contains(true))) {
318					lock();
319					return;
320				}
321			}
322		}
323		unlock();
324
325	}
326
327	private void setDone() {
328		mSaveButton.setText(getString(R.string.done));
329	}
330
331	private void setFetching() {
332		mSaveButton.setText(getString(R.string.fetching_keys));
333	}
334}