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 null
131 );
132 }
133 for(final String fingerprint : foreignKeysToTrust.keySet()) {
134 hasForeignKeys = true;
135 addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), fingerprint, false,
136 XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(fingerprint)), false,
137 new CompoundButton.OnCheckedChangeListener() {
138 @Override
139 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
140 foreignKeysToTrust.put(fingerprint, isChecked);
141 lockOrUnlockAsNeeded();
142 }
143 },
144 null,
145 null
146 );
147 }
148
149 if(hasOwnKeys) {
150 ownKeysTitle.setText(accountJid.toString());
151 ownKeysCard.setVisibility(View.VISIBLE);
152 }
153 if(hasForeignKeys) {
154 foreignKeysTitle.setText(contactJid.toString());
155 foreignKeysCard.setVisibility(View.VISIBLE);
156 }
157 if(hasPendingKeyFetches()) {
158 setFetching();
159 lock();
160 } else {
161 if (!hasForeignKeys && hasNoOtherTrustedKeys()) {
162 keyErrorMessageCard.setVisibility(View.VISIBLE);
163 keyErrorMessage.setText(R.string.error_no_keys_to_trust);
164 ownKeys.removeAllViews(); ownKeysCard.setVisibility(View.GONE);
165 foreignKeys.removeAllViews(); foreignKeysCard.setVisibility(View.GONE);
166 }
167 lockOrUnlockAsNeeded();
168 setDone();
169 }
170 }
171
172 private boolean reloadFingerprints() {
173 ownKeysToTrust.clear();
174 foreignKeysToTrust.clear();
175 AxolotlService service = this.mAccount.getAxolotlService();
176 Set<IdentityKey> ownKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED);
177 Set<IdentityKey> foreignKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, contact);
178 if (hasNoOtherTrustedKeys() && ownKeysSet.size() == 0) {
179 foreignKeysSet.addAll(service.getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, contact));
180 }
181 for(final IdentityKey identityKey : ownKeysSet) {
182 if(!ownKeysToTrust.containsKey(identityKey)) {
183 ownKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
184 }
185 }
186 for(final IdentityKey identityKey : foreignKeysSet) {
187 if(!foreignKeysToTrust.containsKey(identityKey)) {
188 foreignKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
189 }
190 }
191 return ownKeysSet.size() + foreignKeysSet.size() > 0;
192 }
193
194 @Override
195 public void onBackendConnected() {
196 if ((accountJid != null) && (contactJid != null)) {
197 this.mAccount = xmppConnectionService.findAccountByJid(accountJid);
198 if (this.mAccount == null) {
199 return;
200 }
201 this.contact = this.mAccount.getRoster().getContact(contactJid);
202 reloadFingerprints();
203 populateView();
204 }
205 }
206
207 private boolean hasNoOtherTrustedKeys() {
208 return mAccount == null || mAccount.getAxolotlService().getNumTrustedKeys(contact) == 0;
209 }
210
211 private boolean hasPendingKeyFetches() {
212 return mAccount != null && contact != null && mAccount.getAxolotlService().hasPendingKeyFetches(mAccount,contact);
213 }
214
215
216 @Override
217 public void onKeyStatusUpdated(final AxolotlService.FetchStatus report) {
218 if (report != null) {
219 runOnUiThread(new Runnable() {
220 @Override
221 public void run() {
222 switch (report) {
223 case ERROR:
224 Toast.makeText(TrustKeysActivity.this,R.string.error_fetching_omemo_key,Toast.LENGTH_SHORT).show();
225 break;
226 case SUCCESS_VERIFIED:
227 Toast.makeText(TrustKeysActivity.this,R.string.verified_omemo_key_with_certificate,Toast.LENGTH_LONG).show();
228 break;
229 }
230 }
231 });
232
233 }
234 boolean keysToTrust = reloadFingerprints();
235 if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) {
236 refreshUi();
237 } else {
238 runOnUiThread(new Runnable() {
239 @Override
240 public void run() {
241 finishOk();
242 }
243 });
244
245 }
246 }
247
248 private void finishOk() {
249 Intent data = new Intent();
250 data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID));
251 setResult(RESULT_OK, data);
252 finish();
253 }
254
255 private void commitTrusts() {
256 for(final String fingerprint :ownKeysToTrust.keySet()) {
257 contact.getAccount().getAxolotlService().setFingerprintTrust(
258 fingerprint,
259 XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint)));
260 }
261 for(final String fingerprint:foreignKeysToTrust.keySet()) {
262 contact.getAccount().getAxolotlService().setFingerprintTrust(
263 fingerprint,
264 XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(fingerprint)));
265 }
266 }
267
268 private void unlock() {
269 mSaveButton.setEnabled(true);
270 mSaveButton.setTextColor(getPrimaryTextColor());
271 }
272
273 private void lock() {
274 mSaveButton.setEnabled(false);
275 mSaveButton.setTextColor(getSecondaryTextColor());
276 }
277
278 private void lockOrUnlockAsNeeded() {
279 if (hasNoOtherTrustedKeys() && !foreignKeysToTrust.values().contains(true)){
280 lock();
281 } else {
282 unlock();
283 }
284 }
285
286 private void setDone() {
287 mSaveButton.setText(getString(R.string.done));
288 }
289
290 private void setFetching() {
291 mSaveButton.setText(getString(R.string.fetching_keys));
292 }
293}