EnterJidDialog.java

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