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}