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                reconnectAccounts();
 75            }
 76            case AppSettings.AUTOMATIC_MESSAGE_DELETION -> {
 77                requireService().expireOldMessages(true);
 78            }
 79        }
 80    }
 81
 82    @Override
 83    public void onStart() {
 84        super.onStart();
 85        requireActivity().setTitle(R.string.pref_title_security);
 86    }
 87
 88    @Override
 89    public boolean onPreferenceTreeClick(final Preference preference) {
 90        if (REMOVE_TRUSTED_CERTIFICATES.equals(preference.getKey())) {
 91            showRemoveCertificatesDialog();
 92            return true;
 93        }
 94        return super.onPreferenceTreeClick(preference);
 95    }
 96
 97    private void showRemoveCertificatesDialog() {
 98        final MemorizingTrustManager mtm = requireService().getMemorizingTrustManager();
 99        final ArrayList<String> aliases = Collections.list(mtm.getCertificates());
100        if (aliases.isEmpty()) {
101            Toast.makeText(requireActivity(), R.string.toast_no_trusted_certs, Toast.LENGTH_LONG)
102                    .show();
103            return;
104        }
105        final ArrayList<Integer> selectedItems = new ArrayList<>();
106        final MaterialAlertDialogBuilder dialogBuilder =
107                new MaterialAlertDialogBuilder(requireActivity());
108        dialogBuilder.setTitle(getString(R.string.dialog_manage_certs_title));
109        dialogBuilder.setMultiChoiceItems(
110                aliases.toArray(new CharSequence[0]),
111                null,
112                (dialog, indexSelected, isChecked) -> {
113                    if (isChecked) {
114                        selectedItems.add(indexSelected);
115                    } else if (selectedItems.contains(indexSelected)) {
116                        selectedItems.remove(Integer.valueOf(indexSelected));
117                    }
118                    if (dialog instanceof AlertDialog alertDialog) {
119                        alertDialog
120                                .getButton(DialogInterface.BUTTON_POSITIVE)
121                                .setEnabled(!selectedItems.isEmpty());
122                    }
123                });
124
125        dialogBuilder.setPositiveButton(
126                getString(R.string.dialog_manage_certs_positivebutton),
127                (dialog, which) -> confirmCertificateDeletion(aliases, selectedItems));
128        dialogBuilder.setNegativeButton(R.string.cancel, null);
129        final AlertDialog removeCertsDialog = dialogBuilder.create();
130        removeCertsDialog.show();
131        removeCertsDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
132    }
133
134    private void confirmCertificateDeletion(
135            final ArrayList<String> aliases, final ArrayList<Integer> selectedItems) {
136        final int count = selectedItems.size();
137        if (count == 0) {
138            return;
139        }
140        final MemorizingTrustManager mtm = requireService().getMemorizingTrustManager();
141        for (int i = 0; i < count; i++) {
142            try {
143                final int item = Integer.parseInt(selectedItems.get(i).toString());
144                final String alias = aliases.get(item);
145                mtm.deleteCertificate(alias);
146            } catch (final KeyStoreException e) {
147                Toast.makeText(
148                                requireActivity(),
149                                "Error: " + e.getLocalizedMessage(),
150                                Toast.LENGTH_LONG)
151                        .show();
152            }
153        }
154        reconnectAccounts();
155        Toast.makeText(
156                        requireActivity(),
157                        getResources()
158                                .getQuantityString(
159                                        R.plurals.toast_delete_certificates, count, count),
160                        Toast.LENGTH_LONG)
161                .show();
162    }
163
164    private static class MessageDeletionSummaryProvider
165            implements Preference.SummaryProvider<ListPreference> {
166
167        @Nullable
168        @Override
169        public CharSequence provideSummary(@NonNull ListPreference preference) {
170            final Integer value = Ints.tryParse(Strings.nullToEmpty(preference.getValue()));
171            return messageDeletionValueToName(preference.getContext(), value == null ? 0 : value);
172        }
173    }
174
175    private static class OmemoSummaryProvider
176            implements Preference.SummaryProvider<ListPreference> {
177
178        @Nullable
179        @Override
180        public CharSequence provideSummary(@NonNull ListPreference preference) {
181            final var context = preference.getContext();
182            final var sharedPreferences = preference.getSharedPreferences();
183            final String value;
184            if (sharedPreferences == null) {
185                value = null;
186            } else {
187                value =
188                        sharedPreferences.getString(
189                                preference.getKey(),
190                                context.getString(R.string.omemo_setting_default));
191            }
192            return switch (Strings.nullToEmpty(value)) {
193                case "always" -> context.getString(R.string.pref_omemo_setting_summary_always);
194                case "default_off" -> context.getString(
195                        R.string.pref_omemo_setting_summary_default_off);
196                default -> context.getString(R.string.pref_omemo_setting_summary_default_on);
197            };
198        }
199    }
200}