1package eu.siacs.conversations.ui;
2
3import android.app.Activity;
4import android.app.Dialog;
5import android.databinding.DataBindingUtil;
6import android.os.Bundle;
7import android.support.annotation.NonNull;
8import android.support.v4.app.DialogFragment;
9import android.support.v7.app.AlertDialog;
10import android.text.Editable;
11import android.text.TextWatcher;
12import android.view.View;
13import android.widget.ArrayAdapter;
14
15import java.util.ArrayList;
16import java.util.Arrays;
17import java.util.Collection;
18import java.util.List;
19
20import eu.siacs.conversations.Config;
21import eu.siacs.conversations.R;
22import eu.siacs.conversations.databinding.EnterJidDialogBinding;
23import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
24import eu.siacs.conversations.ui.interfaces.OnBackendConnected;
25import eu.siacs.conversations.ui.util.DelayedHintHelper;
26import rocks.xmpp.addr.Jid;
27
28public class EnterJidDialog extends DialogFragment implements OnBackendConnected, TextWatcher {
29
30
31 private static final List<String> SUSPICIOUS_DOMAINS = Arrays.asList("conference","muc","room","rooms","chat");
32
33 private OnEnterJidDialogPositiveListener mListener = null;
34
35 private static final String TITLE_KEY = "title";
36 private static final String POSITIVE_BUTTON_KEY = "positive_button";
37 private static final String PREFILLED_JID_KEY = "prefilled_jid";
38 private static final String ACCOUNT_KEY = "account";
39 private static final String ALLOW_EDIT_JID_KEY = "allow_edit_jid";
40 private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list";
41 private static final String SANITY_CHECK_JID = "sanity_check_jid";
42
43 private KnownHostsAdapter knownHostsAdapter;
44
45 private EnterJidDialogBinding binding;
46 private AlertDialog dialog;
47 private boolean sanityCheckJid = false;
48
49
50 private boolean issuedWarning = false;
51
52 public static EnterJidDialog newInstance(final List<String> activatedAccounts,
53 final String title, final String positiveButton,
54 final String prefilledJid, final String account,
55 boolean allowEditJid, final boolean sanity_check_jid) {
56 EnterJidDialog dialog = new EnterJidDialog();
57 Bundle bundle = new Bundle();
58 bundle.putString(TITLE_KEY, title);
59 bundle.putString(POSITIVE_BUTTON_KEY, positiveButton);
60 bundle.putString(PREFILLED_JID_KEY, prefilledJid);
61 bundle.putString(ACCOUNT_KEY, account);
62 bundle.putBoolean(ALLOW_EDIT_JID_KEY, allowEditJid);
63 bundle.putStringArrayList(ACCOUNTS_LIST_KEY, (ArrayList<String>) activatedAccounts);
64 bundle.putBoolean(SANITY_CHECK_JID, sanity_check_jid);
65 dialog.setArguments(bundle);
66 return dialog;
67 }
68
69 @Override
70 public void onActivityCreated(Bundle savedInstanceState) {
71 super.onActivityCreated(savedInstanceState);
72 setRetainInstance(true);
73 }
74
75 @Override
76 public void onStart() {
77 super.onStart();
78 final Activity activity = getActivity();
79 if (activity instanceof XmppActivity && ((XmppActivity) activity).xmppConnectionService != null) {
80 refreshKnownHosts();
81 }
82 }
83
84 @NonNull
85 @Override
86 public Dialog onCreateDialog(Bundle savedInstanceState) {
87 final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
88 builder.setTitle(getArguments().getString(TITLE_KEY));
89 binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.enter_jid_dialog, null, false);
90 this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.simple_list_item);
91 binding.jid.setAdapter(this.knownHostsAdapter);
92 binding.jid.addTextChangedListener(this);
93 String prefilledJid = getArguments().getString(PREFILLED_JID_KEY);
94 if (prefilledJid != null) {
95 binding.jid.append(prefilledJid);
96 if (!getArguments().getBoolean(ALLOW_EDIT_JID_KEY)) {
97 binding.jid.setFocusable(false);
98 binding.jid.setFocusableInTouchMode(false);
99 binding.jid.setClickable(false);
100 binding.jid.setCursorVisible(false);
101 }
102 }
103 sanityCheckJid = getArguments().getBoolean(SANITY_CHECK_JID, false);
104
105 DelayedHintHelper.setHint(R.string.account_settings_example_jabber_id, binding.jid);
106
107 String account = getArguments().getString(ACCOUNT_KEY);
108 if (account == null) {
109 StartConversationActivity.populateAccountSpinner(getActivity(), getArguments().getStringArrayList(ACCOUNTS_LIST_KEY), binding.account);
110 } else {
111 ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(),
112 R.layout.simple_list_item,
113 new String[]{account});
114 binding.account.setEnabled(false);
115 adapter.setDropDownViewResource(R.layout.simple_list_item);
116 binding.account.setAdapter(adapter);
117 }
118
119
120
121 builder.setView(binding.getRoot());
122 builder.setNegativeButton(R.string.cancel, null);
123 builder.setPositiveButton(getArguments().getString(POSITIVE_BUTTON_KEY), null);
124 this.dialog = builder.create();
125
126 View.OnClickListener dialogOnClick = v -> {
127 handleEnter(binding, account);
128 };
129
130 binding.jid.setOnEditorActionListener((v, actionId, event) -> {
131 handleEnter(binding, account);
132 return true;
133 });
134
135 dialog.show();
136 dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(dialogOnClick);
137 return dialog;
138 }
139
140 private void handleEnter(EnterJidDialogBinding binding, String account) {
141 final Jid accountJid;
142 if (!binding.account.isEnabled() && account == null) {
143 return;
144 }
145 try {
146 if (Config.DOMAIN_LOCK != null) {
147 accountJid = Jid.of((String) binding.account.getSelectedItem(), Config.DOMAIN_LOCK, null);
148 } else {
149 accountJid = Jid.of((String) binding.account.getSelectedItem());
150 }
151 } catch (final IllegalArgumentException e) {
152 return;
153 }
154 final Jid contactJid;
155 try {
156 contactJid = Jid.of(binding.jid.getText().toString());
157 } catch (final IllegalArgumentException e) {
158 binding.jid.setError(getActivity().getString(R.string.invalid_jid));
159 return;
160 }
161
162 if (!issuedWarning && sanityCheckJid) {
163 if (contactJid.isDomainJid()) {
164 binding.jid.setError(getActivity().getString(R.string.this_looks_like_a_domain));
165 dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add_anway);
166 issuedWarning = true;
167 return;
168 }
169 if (suspiciousSubDomain(contactJid.getDomain())) {
170 binding.jid.setError(getActivity().getString(R.string.this_looks_like_channel));
171 dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add_anway);
172 issuedWarning = true;
173 return;
174 }
175 }
176
177 if (mListener != null) {
178 try {
179 if (mListener.onEnterJidDialogPositive(accountJid, contactJid)) {
180 dialog.dismiss();
181 }
182 } catch (JidError error) {
183 binding.jid.setError(error.toString());
184 }
185 }
186 }
187
188 public void setOnEnterJidDialogPositiveListener(OnEnterJidDialogPositiveListener listener) {
189 this.mListener = listener;
190 }
191
192 @Override
193 public void onBackendConnected() {
194 refreshKnownHosts();
195 }
196
197 private void refreshKnownHosts() {
198 Activity activity = getActivity();
199 if (activity instanceof XmppActivity) {
200 Collection<String> hosts = ((XmppActivity) activity).xmppConnectionService.getKnownHosts();
201 this.knownHostsAdapter.refresh(hosts);
202 }
203 }
204
205 @Override
206 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
207
208 }
209
210 @Override
211 public void onTextChanged(CharSequence s, int start, int before, int count) {
212
213 }
214
215 @Override
216 public void afterTextChanged(Editable s) {
217 if (issuedWarning) {
218 dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add);
219 issuedWarning = false;
220 }
221 }
222
223 public interface OnEnterJidDialogPositiveListener {
224 boolean onEnterJidDialogPositive(Jid account, Jid contact) throws EnterJidDialog.JidError;
225 }
226
227 public static class JidError extends Exception {
228 final String msg;
229
230 public JidError(final String msg) {
231 this.msg = msg;
232 }
233
234 public String toString() {
235 return msg;
236 }
237 }
238
239 @Override
240 public void onDestroyView() {
241 Dialog dialog = getDialog();
242 if (dialog != null && getRetainInstance()) {
243 dialog.setDismissMessage(null);
244 }
245 super.onDestroyView();
246 }
247
248 private static boolean suspiciousSubDomain(String domain) {
249 final String[] parts = domain.split("\\.");
250 return parts.length >= 3 && SUSPICIOUS_DOMAINS.contains(parts[0]);
251 }
252}