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