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}