SecuritySettingsFragment.java

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