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}