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;
11
12import org.whispersystems.libaxolotl.IdentityKey;
13
14import java.util.HashMap;
15import java.util.Map;
16import java.util.Set;
17
18import eu.siacs.conversations.R;
19import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
20import eu.siacs.conversations.entities.Account;
21import eu.siacs.conversations.entities.Contact;
22import eu.siacs.conversations.entities.Conversation;
23import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
24import eu.siacs.conversations.xmpp.jid.InvalidJidException;
25import eu.siacs.conversations.xmpp.jid.Jid;
26
27public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdated {
28 private Jid accountJid;
29 private Jid contactJid;
30 private boolean hasOtherTrustedKeys = false;
31 private boolean hasPendingFetches = false;
32 private boolean hasNoTrustedKeys = true;
33
34 private Contact contact;
35 private TextView keyErrorMessage;
36 private LinearLayout keyErrorMessageCard;
37 private TextView ownKeysTitle;
38 private LinearLayout ownKeys;
39 private LinearLayout ownKeysCard;
40 private TextView foreignKeysTitle;
41 private LinearLayout foreignKeys;
42 private LinearLayout foreignKeysCard;
43 private Button mSaveButton;
44 private Button mCancelButton;
45
46 private final Map<IdentityKey, Boolean> ownKeysToTrust = new HashMap<>();
47 private final Map<IdentityKey, Boolean> foreignKeysToTrust = new HashMap<>();
48
49 private final OnClickListener mSaveButtonListener = new OnClickListener() {
50 @Override
51 public void onClick(View v) {
52 commitTrusts();
53 Intent data = new Intent();
54 data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID));
55 setResult(RESULT_OK, data);
56 finish();
57 }
58 };
59
60 private final OnClickListener mCancelButtonListener = new OnClickListener() {
61 @Override
62 public void onClick(View v) {
63 setResult(RESULT_CANCELED);
64 finish();
65 }
66 };
67
68 @Override
69 protected void refreshUiReal() {
70 invalidateOptionsMenu();
71 populateView();
72 }
73
74 @Override
75 protected String getShareableUri() {
76 if (contact != null) {
77 return contact.getShareableUri();
78 } else {
79 return "";
80 }
81 }
82
83 @Override
84 protected void onCreate(final Bundle savedInstanceState) {
85 super.onCreate(savedInstanceState);
86 setContentView(R.layout.activity_trust_keys);
87 try {
88 this.accountJid = Jid.fromString(getIntent().getExtras().getString("account"));
89 } catch (final InvalidJidException ignored) {
90 }
91 try {
92 this.contactJid = Jid.fromString(getIntent().getExtras().getString("contact"));
93 } catch (final InvalidJidException ignored) {
94 }
95 hasNoTrustedKeys = getIntent().getBooleanExtra("has_no_trusted", false);
96
97 keyErrorMessageCard = (LinearLayout) findViewById(R.id.key_error_message_card);
98 keyErrorMessage = (TextView) findViewById(R.id.key_error_message);
99 ownKeysTitle = (TextView) findViewById(R.id.own_keys_title);
100 ownKeys = (LinearLayout) findViewById(R.id.own_keys_details);
101 ownKeysCard = (LinearLayout) findViewById(R.id.own_keys_card);
102 foreignKeysTitle = (TextView) findViewById(R.id.foreign_keys_title);
103 foreignKeys = (LinearLayout) findViewById(R.id.foreign_keys_details);
104 foreignKeysCard = (LinearLayout) findViewById(R.id.foreign_keys_card);
105 mCancelButton = (Button) findViewById(R.id.cancel_button);
106 mCancelButton.setOnClickListener(mCancelButtonListener);
107 mSaveButton = (Button) findViewById(R.id.save_button);
108 mSaveButton.setOnClickListener(mSaveButtonListener);
109
110
111 if (getActionBar() != null) {
112 getActionBar().setHomeButtonEnabled(true);
113 getActionBar().setDisplayHomeAsUpEnabled(true);
114 }
115 }
116
117 private void populateView() {
118 setTitle(getString(R.string.trust_omemo_fingerprints));
119 ownKeys.removeAllViews();
120 foreignKeys.removeAllViews();
121 boolean hasOwnKeys = false;
122 boolean hasForeignKeys = false;
123 for(final IdentityKey identityKey : ownKeysToTrust.keySet()) {
124 hasOwnKeys = true;
125 addFingerprintRowWithListeners(ownKeys, contact.getAccount(), identityKey, false,
126 XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(identityKey)), false,
127 new CompoundButton.OnCheckedChangeListener() {
128 @Override
129 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
130 ownKeysToTrust.put(identityKey, isChecked);
131 // own fingerprints have no impact on locked status.
132 }
133 },
134 null
135 );
136 }
137 for(final IdentityKey identityKey : foreignKeysToTrust.keySet()) {
138 hasForeignKeys = true;
139 addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), identityKey, false,
140 XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(identityKey)), false,
141 new CompoundButton.OnCheckedChangeListener() {
142 @Override
143 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
144 foreignKeysToTrust.put(identityKey, isChecked);
145 lockOrUnlockAsNeeded();
146 }
147 },
148 null
149 );
150 }
151
152 if(hasOwnKeys) {
153 ownKeysTitle.setText(accountJid.toString());
154 ownKeysCard.setVisibility(View.VISIBLE);
155 }
156 if(hasForeignKeys) {
157 foreignKeysTitle.setText(contactJid.toString());
158 foreignKeysCard.setVisibility(View.VISIBLE);
159 }
160 if(hasPendingFetches) {
161 setFetching();
162 lock();
163 } else {
164 if (!hasForeignKeys && !hasOtherTrustedKeys) {
165 keyErrorMessageCard.setVisibility(View.VISIBLE);
166 keyErrorMessage.setText(R.string.error_no_keys_to_trust);
167 ownKeys.removeAllViews(); ownKeysCard.setVisibility(View.GONE);
168 foreignKeys.removeAllViews(); foreignKeysCard.setVisibility(View.GONE);
169 }
170 lockOrUnlockAsNeeded();
171 setDone();
172 }
173 }
174
175 private void getFingerprints(final Account account) {
176 Set<IdentityKey> ownKeysSet = account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED);
177 Set<IdentityKey> foreignKeysSet = account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, contact);
178 if (hasNoTrustedKeys) {
179 ownKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED));
180 foreignKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, contact));
181 }
182 for(final IdentityKey identityKey : ownKeysSet) {
183 if(!ownKeysToTrust.containsKey(identityKey)) {
184 ownKeysToTrust.put(identityKey, false);
185 }
186 }
187 for(final IdentityKey identityKey : foreignKeysSet) {
188 if(!foreignKeysToTrust.containsKey(identityKey)) {
189 foreignKeysToTrust.put(identityKey, false);
190 }
191 }
192 }
193
194 @Override
195 public void onBackendConnected() {
196 if ((accountJid != null) && (contactJid != null)) {
197 final Account account = xmppConnectionService
198 .findAccountByJid(accountJid);
199 if (account == null) {
200 return;
201 }
202 this.contact = account.getRoster().getContact(contactJid);
203 ownKeysToTrust.clear();
204 foreignKeysToTrust.clear();
205 getFingerprints(account);
206
207 if(account.getAxolotlService().getNumTrustedKeys(contact) > 0) {
208 hasOtherTrustedKeys = true;
209 }
210 Conversation conversation = xmppConnectionService.findOrCreateConversation(account, contactJid, false);
211 if(account.getAxolotlService().hasPendingKeyFetches(conversation)) {
212 hasPendingFetches = true;
213 }
214
215 populateView();
216 }
217 }
218
219 @Override
220 public void onKeyStatusUpdated() {
221 final Account account = xmppConnectionService.findAccountByJid(accountJid);
222 hasPendingFetches = false;
223 getFingerprints(account);
224 refreshUi();
225 }
226
227 private void commitTrusts() {
228 for(IdentityKey identityKey:ownKeysToTrust.keySet()) {
229 contact.getAccount().getAxolotlService().setFingerprintTrust(
230 identityKey.getFingerprint().replaceAll("\\s", ""),
231 XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(identityKey)));
232 }
233 for(IdentityKey identityKey:foreignKeysToTrust.keySet()) {
234 contact.getAccount().getAxolotlService().setFingerprintTrust(
235 identityKey.getFingerprint().replaceAll("\\s", ""),
236 XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(identityKey)));
237 }
238 }
239
240 private void unlock() {
241 mSaveButton.setEnabled(true);
242 mSaveButton.setTextColor(getPrimaryTextColor());
243 }
244
245 private void lock() {
246 mSaveButton.setEnabled(false);
247 mSaveButton.setTextColor(getSecondaryTextColor());
248 }
249
250 private void lockOrUnlockAsNeeded() {
251 if (!hasOtherTrustedKeys && !foreignKeysToTrust.values().contains(true)){
252 lock();
253 } else {
254 unlock();
255 }
256 }
257
258 private void setDone() {
259 mSaveButton.setText(getString(R.string.done));
260 }
261
262 private void setFetching() {
263 mSaveButton.setText(getString(R.string.fetching_keys));
264 }
265}