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