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