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