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