SecuritySettingsFragment.java

  1package eu.siacs.conversations.ui.fragment.settings;
  2
  3import android.content.DialogInterface;
  4import android.os.Bundle;
  5import android.widget.Toast;
  6
  7import androidx.annotation.ArrayRes;
  8import androidx.annotation.NonNull;
  9import androidx.annotation.Nullable;
 10import androidx.appcompat.app.AlertDialog;
 11import androidx.preference.ListPreference;
 12import androidx.preference.Preference;
 13
 14import com.google.android.material.dialog.MaterialAlertDialogBuilder;
 15import com.google.common.base.Function;
 16import com.google.common.base.Strings;
 17import com.google.common.primitives.Ints;
 18
 19import eu.siacs.conversations.AppSettings;
 20import eu.siacs.conversations.R;
 21import eu.siacs.conversations.crypto.OmemoSetting;
 22import eu.siacs.conversations.services.MemorizingTrustManager;
 23
 24import java.security.KeyStoreException;
 25import java.util.ArrayList;
 26import java.util.Collections;
 27import java.util.concurrent.Callable;
 28
 29public class SecuritySettingsFragment extends XmppPreferenceFragment {
 30
 31    private static final String REMOVE_TRUSTED_CERTIFICATES = "remove_trusted_certificates";
 32
 33    @Override
 34    public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
 35        setPreferencesFromResource(R.xml.preferences_security, rootKey);
 36        final ListPreference omemo = findPreference(AppSettings.OMEMO);
 37        final ListPreference automaticMessageDeletion =
 38                findPreference(AppSettings.AUTOMATIC_MESSAGE_DELETION);
 39        if (omemo == null || automaticMessageDeletion == null) {
 40            throw new IllegalStateException("The preference resource file is missing preferences");
 41        }
 42        omemo.setSummaryProvider(new OmemoSummaryProvider());
 43        setValues(
 44                automaticMessageDeletion,
 45                R.array.automatic_message_deletion_values,
 46                value -> timeframeValueToName(requireContext(), value));
 47    }
 48
 49    @Override
 50    protected void onSharedPreferenceChanged(@NonNull String key) {
 51        super.onSharedPreferenceChanged(key);
 52        switch (key) {
 53            case AppSettings.OMEMO -> {
 54                OmemoSetting.load(requireContext());
 55            }
 56            case AppSettings.TRUST_SYSTEM_CA_STORE -> {
 57                requireService().updateMemorizingTrustManager();
 58                reconnectAccounts();
 59            }
 60            case AppSettings.REQUIRE_CHANNEL_BINDING -> {
 61                reconnectAccounts();
 62            }
 63            case AppSettings.AUTOMATIC_MESSAGE_DELETION -> {
 64                requireService().expireOldMessages(true);
 65            }
 66        }
 67    }
 68
 69    @Override
 70    public void onStart() {
 71        super.onStart();
 72        requireActivity().setTitle(R.string.pref_title_security);
 73    }
 74
 75    @Override
 76    public boolean onPreferenceTreeClick(final Preference preference) {
 77        if (REMOVE_TRUSTED_CERTIFICATES.equals(preference.getKey())) {
 78            showRemoveCertificatesDialog();
 79            return true;
 80        }
 81        return super.onPreferenceTreeClick(preference);
 82    }
 83
 84    private void showRemoveCertificatesDialog() {
 85        final MemorizingTrustManager mtm = requireService().getMemorizingTrustManager();
 86        final ArrayList<String> aliases = Collections.list(mtm.getCertificates());
 87        if (aliases.isEmpty()) {
 88            Toast.makeText(requireActivity(), R.string.toast_no_trusted_certs, Toast.LENGTH_LONG)
 89                    .show();
 90            return;
 91        }
 92        final ArrayList<Integer> selectedItems = new ArrayList<>();
 93        final MaterialAlertDialogBuilder dialogBuilder =
 94                new MaterialAlertDialogBuilder(requireActivity());
 95        dialogBuilder.setTitle(getString(R.string.dialog_manage_certs_title));
 96        dialogBuilder.setMultiChoiceItems(
 97                aliases.toArray(new CharSequence[0]),
 98                null,
 99                (dialog, indexSelected, isChecked) -> {
100                    if (isChecked) {
101                        selectedItems.add(indexSelected);
102                    } else if (selectedItems.contains(indexSelected)) {
103                        selectedItems.remove(Integer.valueOf(indexSelected));
104                    }
105                    if (dialog instanceof AlertDialog alertDialog) {
106                        alertDialog
107                                .getButton(DialogInterface.BUTTON_POSITIVE)
108                                .setEnabled(!selectedItems.isEmpty());
109                    }
110                });
111
112        dialogBuilder.setPositiveButton(
113                getString(R.string.dialog_manage_certs_positivebutton),
114                (dialog, which) -> confirmCertificateDeletion(aliases, selectedItems));
115        dialogBuilder.setNegativeButton(R.string.cancel, null);
116        final AlertDialog removeCertsDialog = dialogBuilder.create();
117        removeCertsDialog.show();
118        removeCertsDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
119    }
120
121    private void confirmCertificateDeletion(
122            final ArrayList<String> aliases, final ArrayList<Integer> selectedItems) {
123        final int count = selectedItems.size();
124        if (count == 0) {
125            return;
126        }
127        final MemorizingTrustManager mtm = requireService().getMemorizingTrustManager();
128        for (int i = 0; i < count; i++) {
129            try {
130                final int item = Integer.parseInt(selectedItems.get(i).toString());
131                final String alias = aliases.get(item);
132                mtm.deleteCertificate(alias);
133            } catch (final KeyStoreException e) {
134                Toast.makeText(
135                                requireActivity(),
136                                "Error: " + e.getLocalizedMessage(),
137                                Toast.LENGTH_LONG)
138                        .show();
139            }
140        }
141        reconnectAccounts();
142        Toast.makeText(
143                        requireActivity(),
144                        getResources()
145                                .getQuantityString(
146                                        R.plurals.toast_delete_certificates, count, count),
147                        Toast.LENGTH_LONG)
148                .show();
149    }
150
151    private static class OmemoSummaryProvider
152            implements Preference.SummaryProvider<ListPreference> {
153
154        @Nullable
155        @Override
156        public CharSequence provideSummary(@NonNull ListPreference preference) {
157            final var context = preference.getContext();
158            final var sharedPreferences = preference.getSharedPreferences();
159            final String value;
160            if (sharedPreferences == null) {
161                value = null;
162            } else {
163                value =
164                        sharedPreferences.getString(
165                                preference.getKey(),
166                                context.getString(R.string.omemo_setting_default));
167            }
168            return switch (Strings.nullToEmpty(value)) {
169                case "always" -> context.getString(R.string.pref_omemo_setting_summary_always);
170                case "default_off" -> context.getString(
171                        R.string.pref_omemo_setting_summary_default_off);
172                default -> context.getString(R.string.pref_omemo_setting_summary_default_on);
173            };
174        }
175    }
176}