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.R;
 22import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 23import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
 24import eu.siacs.conversations.entities.Account;
 25import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
 26import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 27import eu.siacs.conversations.xmpp.jid.Jid;
 28
 29public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdated {
 30	private Jid accountJid;
 31	private List<Jid> contactJids;
 32
 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 LinearLayout foreignKeys;
 40	private Button mSaveButton;
 41	private Button mCancelButton;
 42
 43	private AxolotlService.FetchStatus lastFetchReport = AxolotlService.FetchStatus.SUCCESS;
 44
 45	private final Map<String, Boolean> ownKeysToTrust = new HashMap<>();
 46	private final Map<Jid,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 void onCreate(final Bundle savedInstanceState) {
 72		super.onCreate(savedInstanceState);
 73		setContentView(R.layout.activity_trust_keys);
 74		try {
 75			this.accountJid = Jid.fromString(getIntent().getExtras().getString(EXTRA_ACCOUNT));
 76		} catch (final InvalidJidException ignored) {
 77		}
 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					XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint)), 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					null,
123					null
124			);
125		}
126
127		synchronized (this.foreignKeysToTrust) {
128			for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) {
129				final LinearLayout layout = (LinearLayout) getLayoutInflater().inflate(R.layout.keys_card, foreignKeys, false);
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				header.setText(entry.getKey().toString());
133				final Map<String, Boolean> fingerprints = entry.getValue();
134				for (final String fingerprint : fingerprints.keySet()) {
135					hasForeignKeys = true;
136					addFingerprintRowWithListeners(keysContainer, mAccount, fingerprint, false,
137							XmppAxolotlSession.Trust.fromBoolean(fingerprints.get(fingerprint)), 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							null,
146							null
147					);
148				}
149				foreignKeys.addView(layout);
150			}
151		}
152
153		ownKeysTitle.setText(accountJid.toString());
154		ownKeysCard.setVisibility(hasOwnKeys ? View.VISIBLE : View.GONE);
155		foreignKeys.setVisibility(hasForeignKeys ? View.VISIBLE : View.GONE);
156		if(hasPendingKeyFetches()) {
157			setFetching();
158			lock();
159		} else {
160			if (!hasForeignKeys && hasNoOtherTrustedKeys()) {
161				keyErrorMessageCard.setVisibility(View.VISIBLE);
162				if (lastFetchReport == AxolotlService.FetchStatus.ERROR
163						|| mAccount.getAxolotlService().fetchMapHasErrors(contactJids)) {
164					keyErrorMessage.setText(R.string.error_no_keys_to_trust_server_error);
165				} else {
166					keyErrorMessage.setText(R.string.error_no_keys_to_trust);
167				}
168				ownKeys.removeAllViews();
169				ownKeysCard.setVisibility(View.GONE);
170				foreignKeys.removeAllViews();
171				foreignKeys.setVisibility(View.GONE);
172			}
173			lockOrUnlockAsNeeded();
174			setDone();
175		}
176	}
177
178	private boolean reloadFingerprints() {
179		ownKeysToTrust.clear();
180		AxolotlService service = this.mAccount.getAxolotlService();
181		Set<IdentityKey> ownKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED);
182		for(final IdentityKey identityKey : ownKeysSet) {
183			if(!ownKeysToTrust.containsKey(identityKey)) {
184				ownKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
185			}
186		}
187		synchronized (this.foreignKeysToTrust) {
188			foreignKeysToTrust.clear();
189			for (Jid jid : contactJids) {
190				Set<IdentityKey> foreignKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, jid);
191				if (hasNoOtherTrustedKeys(jid) && ownKeysSet.size() == 0) {
192					foreignKeysSet.addAll(service.getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, jid));
193				}
194				Map<String, Boolean> foreignFingerprints = new HashMap<>();
195				for (final IdentityKey identityKey : foreignKeysSet) {
196					if (!foreignFingerprints.containsKey(identityKey)) {
197						foreignFingerprints.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
198					}
199				}
200				if (foreignFingerprints.size() > 0) {
201					foreignKeysToTrust.put(jid, foreignFingerprints);
202				}
203			}
204		}
205		return ownKeysSet.size() + foreignKeysToTrust.size() > 0;
206	}
207
208	@Override
209	public void onBackendConnected() {
210		if (accountJid != null) {
211			this.mAccount = xmppConnectionService.findAccountByJid(accountJid);
212			if (this.mAccount == null) {
213				return;
214			}
215			reloadFingerprints();
216			populateView();
217		}
218	}
219
220	private boolean hasNoOtherTrustedKeys() {
221		return mAccount == null || mAccount.getAxolotlService().anyTargetHasNoTrustedKeys(contactJids);
222	}
223
224	private boolean hasNoOtherTrustedKeys(Jid contact) {
225		return mAccount == null || mAccount.getAxolotlService().getNumTrustedKeys(contact) == 0;
226	}
227
228	private boolean hasPendingKeyFetches() {
229		return mAccount != null && mAccount.getAxolotlService().hasPendingKeyFetches(mAccount, contactJids);
230	}
231
232
233	@Override
234	public void onKeyStatusUpdated(final AxolotlService.FetchStatus report) {
235		if (report != null) {
236			lastFetchReport = report;
237			runOnUiThread(new Runnable() {
238				@Override
239				public void run() {
240					switch (report) {
241						case ERROR:
242							Toast.makeText(TrustKeysActivity.this,R.string.error_fetching_omemo_key,Toast.LENGTH_SHORT).show();
243							break;
244						case SUCCESS_VERIFIED:
245							Toast.makeText(TrustKeysActivity.this,R.string.verified_omemo_key_with_certificate,Toast.LENGTH_LONG).show();
246							break;
247					}
248				}
249			});
250
251		}
252		boolean keysToTrust = reloadFingerprints();
253		if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) {
254			refreshUi();
255		} else {
256			runOnUiThread(new Runnable() {
257				@Override
258				public void run() {
259					finishOk();
260				}
261			});
262
263		}
264	}
265
266	private void finishOk() {
267		Intent data = new Intent();
268		data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID));
269		setResult(RESULT_OK, data);
270		finish();
271	}
272
273	private void commitTrusts() {
274		for(final String fingerprint :ownKeysToTrust.keySet()) {
275			mAccount.getAxolotlService().setFingerprintTrust(
276					fingerprint,
277					XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint)));
278		}
279		synchronized (this.foreignKeysToTrust) {
280			for (Map<String, Boolean> value : foreignKeysToTrust.values()) {
281				for (final String fingerprint : value.keySet()) {
282					mAccount.getAxolotlService().setFingerprintTrust(
283							fingerprint,
284							XmppAxolotlSession.Trust.fromBoolean(value.get(fingerprint)));
285				}
286			}
287		}
288	}
289
290	private void unlock() {
291		mSaveButton.setEnabled(true);
292		mSaveButton.setTextColor(getPrimaryTextColor());
293	}
294
295	private void lock() {
296		mSaveButton.setEnabled(false);
297		mSaveButton.setTextColor(getSecondaryTextColor());
298	}
299
300	private void lockOrUnlockAsNeeded() {
301		synchronized (this.foreignKeysToTrust) {
302			for (Jid jid : contactJids) {
303				Map<String, Boolean> fingerprints = foreignKeysToTrust.get(jid);
304				if (hasNoOtherTrustedKeys(jid) && (fingerprints == null || !fingerprints.values().contains(true))) {
305					lock();
306					return;
307				}
308			}
309		}
310		unlock();
311
312	}
313
314	private void setDone() {
315		mSaveButton.setText(getString(R.string.done));
316	}
317
318	private void setFetching() {
319		mSaveButton.setText(getString(R.string.fetching_keys));
320	}
321}