OmemoActivity.java

  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}