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}