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