OmemoActivity.java

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