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.entities.Conversation;
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 Jid contactJid;
32
33 private boolean hasNoTrustedKeys = true;
34
35 private Contact contact;
36 private Account mAccount;
37 private TextView keyErrorMessage;
38 private LinearLayout keyErrorMessageCard;
39 private TextView ownKeysTitle;
40 private LinearLayout ownKeys;
41 private LinearLayout ownKeysCard;
42 private TextView foreignKeysTitle;
43 private LinearLayout foreignKeys;
44 private LinearLayout foreignKeysCard;
45 private Button mSaveButton;
46 private Button mCancelButton;
47
48 private final Map<String, Boolean> ownKeysToTrust = new HashMap<>();
49 private final Map<String, Boolean> foreignKeysToTrust = new HashMap<>();
50
51 private final OnClickListener mSaveButtonListener = new OnClickListener() {
52 @Override
53 public void onClick(View v) {
54 commitTrusts();
55 finishOk();
56 }
57 };
58
59 private final OnClickListener mCancelButtonListener = new OnClickListener() {
60 @Override
61 public void onClick(View v) {
62 setResult(RESULT_CANCELED);
63 finish();
64 }
65 };
66
67 @Override
68 protected void refreshUiReal() {
69 invalidateOptionsMenu();
70 populateView();
71 }
72
73 @Override
74 protected String getShareableUri() {
75 if (contact != null) {
76 return contact.getShareableUri();
77 } else {
78 return "";
79 }
80 }
81
82 @Override
83 protected void onCreate(final Bundle savedInstanceState) {
84 super.onCreate(savedInstanceState);
85 setContentView(R.layout.activity_trust_keys);
86 try {
87 this.accountJid = Jid.fromString(getIntent().getExtras().getString("account"));
88 } catch (final InvalidJidException ignored) {
89 }
90 try {
91 this.contactJid = Jid.fromString(getIntent().getExtras().getString("contact"));
92 } catch (final InvalidJidException ignored) {
93 }
94 hasNoTrustedKeys = getIntent().getBooleanExtra("has_no_trusted", false);
95
96 keyErrorMessageCard = (LinearLayout) findViewById(R.id.key_error_message_card);
97 keyErrorMessage = (TextView) findViewById(R.id.key_error_message);
98 ownKeysTitle = (TextView) findViewById(R.id.own_keys_title);
99 ownKeys = (LinearLayout) findViewById(R.id.own_keys_details);
100 ownKeysCard = (LinearLayout) findViewById(R.id.own_keys_card);
101 foreignKeysTitle = (TextView) findViewById(R.id.foreign_keys_title);
102 foreignKeys = (LinearLayout) findViewById(R.id.foreign_keys_details);
103 foreignKeysCard = (LinearLayout) findViewById(R.id.foreign_keys_card);
104 mCancelButton = (Button) findViewById(R.id.cancel_button);
105 mCancelButton.setOnClickListener(mCancelButtonListener);
106 mSaveButton = (Button) findViewById(R.id.save_button);
107 mSaveButton.setOnClickListener(mSaveButtonListener);
108
109
110 if (getActionBar() != null) {
111 getActionBar().setHomeButtonEnabled(true);
112 getActionBar().setDisplayHomeAsUpEnabled(true);
113 }
114 }
115
116 private void populateView() {
117 setTitle(getString(R.string.trust_omemo_fingerprints));
118 ownKeys.removeAllViews();
119 foreignKeys.removeAllViews();
120 boolean hasOwnKeys = false;
121 boolean hasForeignKeys = false;
122 for(final String fingerprint : ownKeysToTrust.keySet()) {
123 hasOwnKeys = true;
124 addFingerprintRowWithListeners(ownKeys, contact.getAccount(), fingerprint, false,
125 XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint)), false,
126 new CompoundButton.OnCheckedChangeListener() {
127 @Override
128 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
129 ownKeysToTrust.put(fingerprint, isChecked);
130 // own fingerprints have no impact on locked status.
131 }
132 },
133 null
134 );
135 }
136 for(final String fingerprint : foreignKeysToTrust.keySet()) {
137 hasForeignKeys = true;
138 addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), fingerprint, false,
139 XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(fingerprint)), false,
140 new CompoundButton.OnCheckedChangeListener() {
141 @Override
142 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
143 foreignKeysToTrust.put(fingerprint, isChecked);
144 lockOrUnlockAsNeeded();
145 }
146 },
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 keyErrorMessage.setText(R.string.error_no_keys_to_trust);
166 ownKeys.removeAllViews(); ownKeysCard.setVisibility(View.GONE);
167 foreignKeys.removeAllViews(); foreignKeysCard.setVisibility(View.GONE);
168 }
169 lockOrUnlockAsNeeded();
170 setDone();
171 }
172 }
173
174 private boolean reloadFingerprints() {
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 (hasNoTrustedKeys) {
179 ownKeysSet.addAll(service.getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED));
180 foreignKeysSet.addAll(service.getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, contact));
181 }
182 for(final IdentityKey identityKey : ownKeysSet) {
183 if(!ownKeysToTrust.containsKey(identityKey)) {
184 ownKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
185 }
186 }
187 for(final IdentityKey identityKey : foreignKeysSet) {
188 if(!foreignKeysToTrust.containsKey(identityKey)) {
189 foreignKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
190 }
191 }
192 return ownKeysSet.size() + foreignKeysSet.size() > 0;
193 }
194
195 @Override
196 public void onBackendConnected() {
197 if ((accountJid != null) && (contactJid != null)) {
198 this.mAccount = xmppConnectionService.findAccountByJid(accountJid);
199 if (this.mAccount == null) {
200 return;
201 }
202 this.contact = this.mAccount.getRoster().getContact(contactJid);
203 ownKeysToTrust.clear();
204 foreignKeysToTrust.clear();
205 reloadFingerprints();
206 populateView();
207 }
208 }
209
210 private boolean hasNoOtherTrustedKeys() {
211 return mAccount == null || mAccount.getAxolotlService().getNumTrustedKeys(contact) == 0;
212 }
213
214 private boolean hasPendingKeyFetches() {
215 return mAccount != null && contact != null && mAccount.getAxolotlService().hasPendingKeyFetches(mAccount,contact);
216 }
217
218
219 @Override
220 public void onKeyStatusUpdated() {
221 boolean keysToTrust = reloadFingerprints();
222 if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) {
223 refreshUi();
224 } else {
225 runOnUiThread(new Runnable() {
226 @Override
227 public void run() {
228 Toast.makeText(TrustKeysActivity.this, "Nothing to do", Toast.LENGTH_SHORT).show();
229 finishOk();
230 }
231 });
232
233 }
234 }
235
236 private void finishOk() {
237 Intent data = new Intent();
238 data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID));
239 setResult(RESULT_OK, data);
240 finish();
241 }
242
243 private void commitTrusts() {
244 for(final String fingerprint :ownKeysToTrust.keySet()) {
245 contact.getAccount().getAxolotlService().setFingerprintTrust(
246 fingerprint,
247 XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint)));
248 }
249 for(final String fingerprint:foreignKeysToTrust.keySet()) {
250 contact.getAccount().getAxolotlService().setFingerprintTrust(
251 fingerprint,
252 XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(fingerprint)));
253 }
254 }
255
256 private void unlock() {
257 mSaveButton.setEnabled(true);
258 mSaveButton.setTextColor(getPrimaryTextColor());
259 }
260
261 private void lock() {
262 mSaveButton.setEnabled(false);
263 mSaveButton.setTextColor(getSecondaryTextColor());
264 }
265
266 private void lockOrUnlockAsNeeded() {
267 if (hasNoOtherTrustedKeys() && !foreignKeysToTrust.values().contains(true)){
268 lock();
269 } else {
270 unlock();
271 }
272 }
273
274 private void setDone() {
275 mSaveButton.setText(getString(R.string.done));
276 }
277
278 private void setFetching() {
279 mSaveButton.setText(getString(R.string.fetching_keys));
280 }
281}