1package eu.siacs.conversations.ui;
2
3import android.content.Intent;
4import android.view.ContextMenu;
5import android.view.MenuItem;
6import android.view.View;
7import android.widget.CompoundButton;
8import android.widget.LinearLayout;
9import android.widget.Toast;
10import androidx.annotation.NonNull;
11import androidx.databinding.DataBindingUtil;
12import com.google.android.material.color.MaterialColors;
13import com.google.android.material.dialog.MaterialAlertDialogBuilder;
14import eu.siacs.conversations.Config;
15import eu.siacs.conversations.R;
16import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
17import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
18import eu.siacs.conversations.databinding.ItemDeviceFingerprintBinding;
19import eu.siacs.conversations.entities.Account;
20import eu.siacs.conversations.utils.CryptoHelper;
21import eu.siacs.conversations.utils.XmppUri;
22
23public abstract class OmemoActivity extends XmppActivity {
24
25 private Account mSelectedAccount;
26 protected String mSelectedFingerprint;
27
28 protected XmppUri mPendingFingerprintVerificationUri = null;
29
30 @Override
31 public void onCreateContextMenu(
32 ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
33 super.onCreateContextMenu(menu, v, menuInfo);
34 Object account = v.getTag(R.id.TAG_ACCOUNT);
35 Object fingerprint = v.getTag(R.id.TAG_FINGERPRINT);
36 Object fingerprintStatus = v.getTag(R.id.TAG_FINGERPRINT_STATUS);
37 if (account instanceof Account
38 && fingerprint instanceof String
39 && fingerprintStatus instanceof FingerprintStatus) {
40 getMenuInflater().inflate(R.menu.omemo_key_context, menu);
41 MenuItem distrust = menu.findItem(R.id.distrust_key);
42 MenuItem verifyScan = menu.findItem(R.id.verify_scan);
43 if (this instanceof TrustKeysActivity) {
44 distrust.setVisible(false);
45 verifyScan.setVisible(false);
46 } else {
47 FingerprintStatus status = (FingerprintStatus) fingerprintStatus;
48 if (!status.isActive() || status.isVerified()) {
49 verifyScan.setVisible(false);
50 }
51 distrust.setVisible(
52 status.isVerified() || (!status.isActive() && status.isTrusted()));
53 }
54 // TODO can we rework this into using Intents?
55 this.mSelectedAccount = (Account) account;
56 this.mSelectedFingerprint = (String) fingerprint;
57 }
58 }
59
60 @Override
61 public boolean onContextItemSelected(final MenuItem item) {
62 final var itemId = item.getItemId();
63 if (itemId == R.id.distrust_key) {
64 showPurgeKeyDialog(mSelectedAccount, mSelectedFingerprint);
65 return true;
66 } else if (itemId == R.id.copy_omemo_key) {
67 copyOmemoFingerprint(mSelectedFingerprint);
68 return true;
69 } else if (itemId == R.id.verify_scan) {
70 ScanActivity.scan(this);
71 return true;
72 } else {
73 return super.onContextItemSelected(item);
74 }
75 }
76
77 @Override
78 public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
79 super.onActivityResult(requestCode, resultCode, intent);
80 if (requestCode == ScanActivity.REQUEST_SCAN_QR_CODE && resultCode == RESULT_OK) {
81 String result = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
82 XmppUri uri = new XmppUri(result == null ? "" : result);
83 if (xmppConnectionServiceBound) {
84 processFingerprintVerification(uri);
85 } else {
86 this.mPendingFingerprintVerificationUri = uri;
87 }
88 }
89 }
90
91 protected abstract void processFingerprintVerification(XmppUri uri);
92
93 protected void copyOmemoFingerprint(String fingerprint) {
94 if (copyTextToClipboard(
95 CryptoHelper.prettifyFingerprint(fingerprint.substring(2)),
96 R.string.omemo_fingerprint)) {
97 Toast.makeText(this, R.string.toast_message_omemo_fingerprint, Toast.LENGTH_SHORT)
98 .show();
99 }
100 }
101
102 protected void addFingerprintRow(
103 LinearLayout keys, final XmppAxolotlSession session, boolean highlight) {
104 final Account account = session.getAccount();
105 final String fingerprint = session.getFingerprint();
106 addFingerprintRowWithListeners(
107 keys,
108 session.getAccount(),
109 fingerprint,
110 highlight,
111 session.getTrust(),
112 true,
113 true,
114 (buttonView, isChecked) ->
115 account.getAxolotlService()
116 .setFingerprintTrust(
117 fingerprint, FingerprintStatus.createActive(isChecked)));
118 }
119
120 protected void addFingerprintRowWithListeners(
121 LinearLayout keys,
122 final Account account,
123 final String fingerprint,
124 boolean highlight,
125 FingerprintStatus status,
126 boolean showTag,
127 boolean undecidedNeedEnablement,
128 CompoundButton.OnCheckedChangeListener onCheckedChangeListener) {
129 ItemDeviceFingerprintBinding binding =
130 DataBindingUtil.inflate(
131 getLayoutInflater(), R.layout.item_device_fingerprint, keys, true);
132 binding.tglTrust.setVisibility(View.VISIBLE);
133 registerForContextMenu(binding.getRoot());
134 binding.getRoot().setTag(R.id.TAG_ACCOUNT, account);
135 binding.getRoot().setTag(R.id.TAG_FINGERPRINT, fingerprint);
136 binding.getRoot().setTag(R.id.TAG_FINGERPRINT_STATUS, status);
137 boolean x509 =
138 Config.X509_VERIFICATION
139 && status.getTrust() == FingerprintStatus.Trust.VERIFIED_X509;
140 final View.OnClickListener toast;
141 binding.tglTrust.setChecked(status.isTrusted());
142 binding.tglTrust.jumpDrawablesToCurrentState();
143
144 if (status.isActive()) {
145 binding.key.setTextColor(
146 MaterialColors.getColor(
147 binding.key, com.google.android.material.R.attr.colorOnSurface));
148 binding.keyType.setTextColor(
149 MaterialColors.getColor(
150 binding.keyType, com.google.android.material.R.attr.colorOnSurface));
151 if (status.isVerified()) {
152 binding.verifiedFingerprint.setVisibility(View.VISIBLE);
153 binding.verifiedFingerprint.setAlpha(1.0f);
154 binding.tglTrust.setVisibility(View.GONE);
155 binding.verifiedFingerprint.setOnClickListener(
156 v ->
157 replaceToast(
158 getString(R.string.this_device_has_been_verified), false));
159 toast = null;
160 } else {
161 binding.verifiedFingerprint.setVisibility(View.GONE);
162 binding.tglTrust.setVisibility(View.VISIBLE);
163 binding.tglTrust.setOnCheckedChangeListener(onCheckedChangeListener);
164 if (status.getTrust() == FingerprintStatus.Trust.UNDECIDED
165 && undecidedNeedEnablement) {
166 binding.buttonEnableDevice.setVisibility(View.VISIBLE);
167 binding.buttonEnableDevice.setOnClickListener(
168 v -> {
169 account.getAxolotlService()
170 .setFingerprintTrust(
171 fingerprint, FingerprintStatus.createActive(false));
172 binding.buttonEnableDevice.setVisibility(View.GONE);
173 binding.tglTrust.setVisibility(View.VISIBLE);
174 });
175 binding.tglTrust.setVisibility(View.GONE);
176 } else {
177 binding.tglTrust.setOnClickListener(null);
178 binding.tglTrust.setEnabled(true);
179 }
180 toast = v -> hideToast();
181 }
182 } else {
183 binding.key.setTextColor(
184 MaterialColors.getColor(
185 binding.key, com.google.android.material.R.attr.colorOnSurfaceVariant));
186 binding.keyType.setTextColor(
187 MaterialColors.getColor(
188 binding.keyType,
189 com.google.android.material.R.attr.colorOnSurfaceVariant));
190 toast = v -> replaceToast(getString(R.string.this_device_is_no_longer_in_use), false);
191 if (status.isVerified()) {
192 binding.tglTrust.setVisibility(View.GONE);
193 binding.verifiedFingerprint.setVisibility(View.VISIBLE);
194 binding.verifiedFingerprint.setAlpha(0.4368f);
195 binding.verifiedFingerprint.setOnClickListener(toast);
196 } else {
197 binding.tglTrust.setVisibility(View.VISIBLE);
198 binding.verifiedFingerprint.setVisibility(View.GONE);
199 binding.tglTrust.setEnabled(false);
200 }
201 }
202
203 binding.getRoot().setOnClickListener(toast);
204 binding.key.setOnClickListener(toast);
205 binding.keyType.setOnClickListener(toast);
206 if (showTag) {
207 binding.keyType.setText(
208 getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
209 } else {
210 binding.keyType.setVisibility(View.GONE);
211 }
212 if (highlight) {
213 binding.keyType.setTextColor(
214 MaterialColors.getColor(
215 binding.keyType,
216 com.google.android.material.R.attr.colorPrimaryVariant));
217 binding.keyType.setText(
218 getString(
219 x509
220 ? R.string.omemo_fingerprint_x509_selected_message
221 : R.string.omemo_fingerprint_selected_message));
222 } else {
223 binding.keyType.setText(
224 getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
225 }
226
227 binding.key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2)));
228 }
229
230 public void showPurgeKeyDialog(final Account account, final String fingerprint) {
231 final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
232 builder.setTitle(R.string.distrust_omemo_key);
233 builder.setMessage(R.string.distrust_omemo_key_text);
234 builder.setNegativeButton(getString(R.string.cancel), null);
235 builder.setPositiveButton(
236 R.string.confirm,
237 (dialog, which) -> {
238 account.getAxolotlService().distrustFingerprint(fingerprint);
239 refreshUi();
240 });
241 builder.create().show();
242 }
243
244 @Override
245 public void onRequestPermissionsResult(
246 final int requestCode,
247 @NonNull final String[] permissions,
248 @NonNull final int[] grantResults) {
249 super.onRequestPermissionsResult(requestCode, permissions, grantResults);
250 ScanActivity.onRequestPermissionResult(this, requestCode, grantResults);
251 }
252}