EnterJidDialog.java

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