diff options
Diffstat (limited to 'src/main/java/eu')
16 files changed, 436 insertions, 74 deletions
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index c8253dd49..43a900109 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -79,6 +79,19 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } } + public boolean fetchMapHasErrors(Contact contact) { + Jid jid = contact.getJid().toBareJid(); + if (deviceIds.get(jid) != null) { + for (Integer foreignId : this.deviceIds.get(jid)) { + AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId); + if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) { + return true; + } + } + } + return false; + } + private static class AxolotlAddressMap<T> { protected Map<String, Map<Integer, T>> map; protected final Object MAP_LOCK = new Object(); diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index c5831e7ec..f3d891e86 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -743,13 +743,20 @@ public class Message extends AbstractEntity { } public boolean isValidInSession() { - int pastEncryption = this.getPreviousEncryption(); - int futureEncryption = this.getNextEncryption(); + int pastEncryption = getCleanedEncryption(this.getPreviousEncryption()); + int futureEncryption = getCleanedEncryption(this.getNextEncryption()); boolean inUnencryptedSession = pastEncryption == ENCRYPTION_NONE || futureEncryption == ENCRYPTION_NONE || pastEncryption != futureEncryption; - return inUnencryptedSession || this.getEncryption() == pastEncryption; + return inUnencryptedSession || getCleanedEncryption(this.getEncryption()) == pastEncryption; + } + + private static int getCleanedEncryption(int encryption) { + if (encryption == ENCRYPTION_DECRYPTED || encryption == ENCRYPTION_DECRYPTION_FAILED) { + return ENCRYPTION_PGP; + } + return encryption; } } diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 05932acce..4bdf080cf 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -86,7 +86,7 @@ public class FileBackend { public static String getConversationsImageDirectory() { return Environment.getExternalStoragePublicDirectory( - Environment.DIRECTORY_PICTURES).getAbsolutePath() + Environment.DIRECTORY_PICTURES).getAbsolutePath() + "/Conversations/"; } @@ -155,12 +155,7 @@ public class FileBackend { return FileUtils.getPath(mXmppConnectionService,uri); } - public DownloadableFile copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException { - Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage"); - String mime = mXmppConnectionService.getContentResolver().getType(uri); - String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime); - message.setRelativeFilePath(message.getUuid() + "." + extension); - DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message); + public void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException { file.getParentFile().mkdirs(); OutputStream os = null; InputStream is = null; @@ -183,28 +178,18 @@ public class FileBackend { close(os); close(is); } - Log.d(Config.LOGTAG, "output file name " + mXmppConnectionService.getFileBackend().getFile(message)); - return file; + Log.d(Config.LOGTAG, "output file name " + file.getAbsolutePath()); } - public DownloadableFile copyImageToPrivateStorage(Message message, Uri image) - throws FileCopyException { - return this.copyImageToPrivateStorage(message, image, 0); + public void copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException { + Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage"); + String mime = mXmppConnectionService.getContentResolver().getType(uri); + String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime); + message.setRelativeFilePath(message.getUuid() + "." + extension); + copyFileToPrivateStorage(mXmppConnectionService.getFileBackend().getFile(message), uri); } - private DownloadableFile copyImageToPrivateStorage(Message message,Uri image, int sampleSize) throws FileCopyException { - switch(Config.IMAGE_FORMAT) { - case JPEG: - message.setRelativeFilePath(message.getUuid()+".jpg"); - break; - case PNG: - message.setRelativeFilePath(message.getUuid()+".png"); - break; - case WEBP: - message.setRelativeFilePath(message.getUuid()+".webp"); - break; - } - DownloadableFile file = getFile(message); + private void copyImageToPrivateStorage(File file, Uri image, int sampleSize) throws FileCopyException { file.getParentFile().mkdirs(); InputStream is = null; OutputStream os = null; @@ -225,7 +210,6 @@ public class FileBackend { int rotation = getRotation(image); scaledBitmap = rotate(scaledBitmap, rotation); boolean targetSizeReached = false; - long size = 0; int quality = Config.IMAGE_QUALITY; while(!targetSizeReached) { os = new FileOutputStream(file); @@ -234,14 +218,11 @@ public class FileBackend { throw new FileCopyException(R.string.error_compressing_image); } os.flush(); - size = file.getSize(); - targetSizeReached = size <= Config.IMAGE_MAX_SIZE || quality <= 50; + targetSizeReached = file.length() <= Config.IMAGE_MAX_SIZE || quality <= 50; quality -= 5; } - int width = scaledBitmap.getWidth(); - int height = scaledBitmap.getHeight(); - message.setBody(Long.toString(size) + '|' + width + '|' + height); - return file; + scaledBitmap.recycle(); + return; } catch (FileNotFoundException e) { throw new FileCopyException(R.string.error_file_not_found); } catch (IOException e) { @@ -252,7 +233,7 @@ public class FileBackend { } catch (OutOfMemoryError e) { ++sampleSize; if (sampleSize <= 3) { - return copyImageToPrivateStorage(message, image, sampleSize); + copyImageToPrivateStorage(file, image, sampleSize); } else { throw new FileCopyException(R.string.error_out_of_memory); } @@ -264,6 +245,26 @@ public class FileBackend { } } + public void copyImageToPrivateStorage(File file, Uri image) throws FileCopyException { + copyImageToPrivateStorage(file, image, 0); + } + + public void copyImageToPrivateStorage(Message message, Uri image) throws FileCopyException { + switch(Config.IMAGE_FORMAT) { + case JPEG: + message.setRelativeFilePath(message.getUuid()+".jpg"); + break; + case PNG: + message.setRelativeFilePath(message.getUuid()+".png"); + break; + case WEBP: + message.setRelativeFilePath(message.getUuid()+".webp"); + break; + } + copyImageToPrivateStorage(getFile(message), image); + updateFileParams(message); + } + private int getRotation(File file) { return getRotation(Uri.parse("file://"+file.getAbsolutePath())); } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index a295b2cee..d4fda1b86 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -1969,7 +1969,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } } - Element form = query.findChild("x","jabber:x:data"); + Element form = query.findChild("x", "jabber:x:data"); if (form != null) { conversation.getMucOptions().updateFormData(Data.parse(form)); } @@ -2378,7 +2378,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa updateConversationUi(); updateRosterUi(); } else { - Conversation conversation = find(account,avatar.owner.toBareJid()); + Conversation conversation = find(account, avatar.owner.toBareJid()); if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { MucOptions.User user = conversation.getMucOptions().findUser(avatar.owner.getResourcepart()); if (user != null) { @@ -2586,6 +2586,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return Config.FORCE_ORBOT || getPreferences().getBoolean("use_tor", false); } + public boolean showExtendedConnectionOptions() { + return getPreferences().getBoolean("show_connection_options", false); + } + public int unreadCount() { int count = 0; for (Conversation conversation : getConversations()) { diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 0ea393296..a126b344d 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -5,6 +5,7 @@ import android.app.AlertDialog.Builder; import android.app.PendingIntent; import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; @@ -91,7 +92,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private Jid jidToEdit; private boolean mInitMode = false; - private boolean mUseTor = false; + private boolean mShowOptions = false; private Account mAccount; private String messageFingerprint; @@ -133,7 +134,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } String hostname = null; int numericPort = 5222; - if (mUseTor) { + if (mShowOptions) { hostname = mHostname.getText().toString(); final String port = mPort.getText().toString(); if (hostname.contains(" ")) { @@ -511,8 +512,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } } } - this.mUseTor = Config.FORCE_ORBOT || getPreferences().getBoolean("use_tor", false); - this.mNamePort.setVisibility(mUseTor ? View.VISIBLE : View.GONE); + SharedPreferences preferences = getPreferences(); + boolean useTor = Config.FORCE_ORBOT || preferences.getBoolean("use_tor", false); + this.mShowOptions = useTor || preferences.getBoolean("show_connection_options", false); + mHostname.setHint(useTor ? R.string.hostname_or_onion : R.string.hostname_example); + this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE); } @Override @@ -598,7 +602,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate this.mHostname.getEditableText().append(this.mAccount.getHostname()); this.mPort.setText(""); this.mPort.getEditableText().append(String.valueOf(this.mAccount.getPort())); - this.mNamePort.setVisibility(mUseTor ? View.VISIBLE : View.GONE); + this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE); } if (!mInitMode) { @@ -740,12 +744,24 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } } else { if (this.mAccount.errorStatus()) { - this.mAccountJid.setError(getString(this.mAccount.getStatus().getReadableId())); + final EditText errorTextField; + if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED) { + errorTextField = this.mPassword; + } else if (mShowOptions + && this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND + && this.mHostname.getText().length() > 0) { + errorTextField = this.mHostname; + } else { + errorTextField = this.mAccountJid; + } + errorTextField.setError(getString(this.mAccount.getStatus().getReadableId())); if (init || !accountInfoEdited()) { - this.mAccountJid.requestFocus(); + errorTextField.requestFocus(); } } else { this.mAccountJid.setError(null); + this.mPassword.setError(null); + this.mHostname.setError(null); } this.mStats.setVisibility(View.GONE); } diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index 29da0ce6c..eec30798e 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -162,7 +162,8 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate } else { if (!hasForeignKeys && hasNoOtherTrustedKeys()) { keyErrorMessageCard.setVisibility(View.VISIBLE); - if (lastFetchReport == AxolotlService.FetchStatus.ERROR) { + if (lastFetchReport == AxolotlService.FetchStatus.ERROR + || contact.getAccount().getAxolotlService().fetchMapHasErrors(contact)) { keyErrorMessage.setText(R.string.error_no_keys_to_trust_server_error); } else { keyErrorMessage.setText(R.string.error_no_keys_to_trust); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java index 4be4931f7..47414f900 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java @@ -78,9 +78,10 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> { } final Jid jid = item.getJid(); if (jid != null) { + tvJid.setVisibility(View.VISIBLE); tvJid.setText(jid.toString()); } else { - tvJid.setText(""); + tvJid.setVisibility(View.GONE); } tvName.setText(item.getDisplayName()); loadAvatar(item,picture); diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormBooleanFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormBooleanFieldWrapper.java new file mode 100644 index 000000000..6cb357a90 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/forms/FormBooleanFieldWrapper.java @@ -0,0 +1,80 @@ +package eu.siacs.conversations.ui.forms; + +import android.content.Context; +import android.widget.CheckBox; +import android.widget.CompoundButton; + +import java.util.ArrayList; +import java.util.List; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.xmpp.forms.Field; + +public class FormBooleanFieldWrapper extends FormFieldWrapper { + + protected CheckBox checkBox; + + protected FormBooleanFieldWrapper(Context context, Field field) { + super(context, field); + checkBox = (CheckBox) view.findViewById(R.id.field); + checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + checkBox.setError(null); + invokeOnFormFieldValuesEdited(); + } + }); + } + + @Override + protected void setLabel(String label, boolean required) { + CheckBox checkBox = (CheckBox) view.findViewById(R.id.field); + checkBox.setText(createSpannableLabelString(label, required)); + } + + @Override + public List<String> getValues() { + List<String> values = new ArrayList<>(); + values.add(Boolean.toString(checkBox.isChecked())); + return values; + } + + @Override + protected void setValues(List<String> values) { + if (values.size() == 0) { + checkBox.setChecked(false); + } else { + checkBox.setChecked(Boolean.parseBoolean(values.get(0))); + } + } + + @Override + public boolean validates() { + if (checkBox.isChecked() || !field.isRequired()) { + return true; + } else { + checkBox.setError(context.getString(R.string.this_field_is_required)); + checkBox.requestFocus(); + return false; + } + } + + @Override + public boolean edited() { + if (field.getValues().size() == 0) { + return checkBox.isChecked(); + } else { + return super.edited(); + } + } + + @Override + protected int getLayoutResource() { + return R.layout.form_boolean; + } + + @Override + void setReadOnly(boolean readOnly) { + checkBox.setEnabled(!readOnly); + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldFactory.java b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldFactory.java index 9e54678a3..ee3064726 100644 --- a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldFactory.java +++ b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldFactory.java @@ -15,10 +15,16 @@ public class FormFieldFactory { static { typeTable.put("text-single", FormTextFieldWrapper.class); typeTable.put("text-multi", FormTextFieldWrapper.class); + typeTable.put("text-private", FormTextFieldWrapper.class); + typeTable.put("jid-single", FormJidSingleFieldWrapper.class); + typeTable.put("boolean", FormBooleanFieldWrapper.class); } - public static FormFieldWrapper createFromField(Context context, Field field) { + protected static FormFieldWrapper createFromField(Context context, Field field) { Class clazz = typeTable.get(field.getType()); + if (clazz == null) { + clazz = FormTextFieldWrapper.class; + } return FormFieldWrapper.createFromField(clazz, context, field); } } diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java index 3af375363..3a21ade3b 100644 --- a/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java +++ b/src/main/java/eu/siacs/conversations/ui/forms/FormFieldWrapper.java @@ -1,11 +1,17 @@ package eu.siacs.conversations.ui.forms; import android.content.Context; +import android.text.SpannableString; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import java.util.List; +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; import eu.siacs.conversations.xmpp.forms.Field; public abstract class FormFieldWrapper { @@ -13,20 +19,25 @@ public abstract class FormFieldWrapper { protected final Context context; protected final Field field; protected final View view; + protected OnFormFieldValuesEdited onFormFieldValuesEditedListener; protected FormFieldWrapper(Context context, Field field) { this.context = context; this.field = field; LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); this.view = inflater.inflate(getLayoutResource(), null); - setLabel(field.getLabel(), field.isRequired()); + String label = field.getLabel(); + if (label == null) { + label = field.getFieldName(); + } + setLabel(label, field.isRequired()); } - public void submit() { + public final void submit() { this.field.setValues(getValues()); } - public View getView() { + public final View getView() { return view; } @@ -34,14 +45,51 @@ public abstract class FormFieldWrapper { abstract List<String> getValues(); + protected abstract void setValues(List<String> values); + + abstract boolean validates(); + abstract protected int getLayoutResource(); + abstract void setReadOnly(boolean readOnly); + + protected SpannableString createSpannableLabelString(String label, boolean required) { + SpannableString spannableString = new SpannableString(label + (required ? " *" : "")); + if (required) { + int start = label.length(); + int end = label.length() + 2; + spannableString.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), start, end, 0); + spannableString.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.accent)), start, end, 0); + } + return spannableString; + } + + protected void invokeOnFormFieldValuesEdited() { + if (this.onFormFieldValuesEditedListener != null) { + this.onFormFieldValuesEditedListener.onFormFieldValuesEdited(); + } + } + + public boolean edited() { + return !field.getValues().equals(getValues()); + } + + public void setOnFormFieldValuesEditedListener(OnFormFieldValuesEdited listener) { + this.onFormFieldValuesEditedListener = listener; + } + protected static <F extends FormFieldWrapper> FormFieldWrapper createFromField(Class<F> c, Context context, Field field) { try { - return c.getDeclaredConstructor(Context.class, Field.class).newInstance(context,field); + F fieldWrapper = c.getDeclaredConstructor(Context.class, Field.class).newInstance(context,field); + fieldWrapper.setValues(field.getValues()); + return fieldWrapper; } catch (Exception e) { e.printStackTrace(); return null; } } + + public interface OnFormFieldValuesEdited { + void onFormFieldValuesEdited(); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormJidSingleFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormJidSingleFieldWrapper.java new file mode 100644 index 000000000..553e8f21f --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/forms/FormJidSingleFieldWrapper.java @@ -0,0 +1,44 @@ +package eu.siacs.conversations.ui.forms; + +import android.content.Context; +import android.text.InputType; + +import java.util.List; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.xmpp.forms.Field; +import eu.siacs.conversations.xmpp.jid.InvalidJidException; +import eu.siacs.conversations.xmpp.jid.Jid; + +public class FormJidSingleFieldWrapper extends FormTextFieldWrapper { + + protected FormJidSingleFieldWrapper(Context context, Field field) { + super(context, field); + editText.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); + editText.setHint(R.string.account_settings_example_jabber_id); + } + + @Override + public boolean validates() { + String value = getValue(); + if (!value.isEmpty()) { + try { + Jid.fromString(value); + } catch (InvalidJidException e) { + editText.setError(context.getString(R.string.invalid_jid)); + editText.requestFocus(); + return false; + } + } + return super.validates(); + } + + @Override + protected void setValues(List<String> values) { + StringBuilder builder = new StringBuilder(""); + for(String value : values) { + builder.append(value); + } + editText.setText(builder.toString()); + } +} diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormTextFieldWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormTextFieldWrapper.java index 8f70dd37e..b7dac9510 100644 --- a/src/main/java/eu/siacs/conversations/ui/forms/FormTextFieldWrapper.java +++ b/src/main/java/eu/siacs/conversations/ui/forms/FormTextFieldWrapper.java @@ -1,9 +1,9 @@ package eu.siacs.conversations.ui.forms; import android.content.Context; -import android.text.SpannableString; -import android.text.style.ForegroundColorSpan; -import android.text.style.StyleSpan; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; import android.widget.EditText; import android.widget.TextView; @@ -20,33 +20,78 @@ public class FormTextFieldWrapper extends FormFieldWrapper { protected FormTextFieldWrapper(Context context, Field field) { super(context, field); editText = (EditText) view.findViewById(R.id.field); - editText.setSingleLine("text-single".equals(field.getType())); + editText.setSingleLine(!"text-multi".equals(field.getType())); + if ("text-private".equals(field.getType())) { + editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + } + editText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + editText.setError(null); + invokeOnFormFieldValuesEdited(); + } + + @Override + public void afterTextChanged(Editable s) { + } + }); } @Override protected void setLabel(String label, boolean required) { TextView textView = (TextView) view.findViewById(R.id.label); - SpannableString spannableString = new SpannableString(label + (required ? " *" : "")); - if (required) { - int start = label.length(); - int end = label.length() + 2; - spannableString.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), start, end, 0); - spannableString.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.accent)), start, end, 0); - } - textView.setText(spannableString); + textView.setText(createSpannableLabelString(label, required)); + } + + protected String getValue() { + return editText.getText().toString(); } @Override - List<String> getValues() { + public List<String> getValues() { List<String> values = new ArrayList<>(); - for (String line : editText.getText().toString().split("\\n")) { - values.add(line); + for (String line : getValue().split("\\n")) { + if (line.length() > 0) { + values.add(line); + } } return values; } @Override + protected void setValues(List<String> values) { + StringBuilder builder = new StringBuilder(""); + for(int i = 0; i < values.size(); ++i) { + builder.append(values.get(i)); + if (i < values.size() - 1 && "text-multi".equals(field.getType())) { + builder.append("\n"); + } + } + editText.setText(builder.toString()); + } + + @Override + public boolean validates() { + if (getValue().trim().length() > 0 || !field.isRequired()) { + return true; + } else { + editText.setError(context.getString(R.string.this_field_is_required)); + editText.requestFocus(); + return false; + } + } + + @Override protected int getLayoutResource() { return R.layout.form_text; } + + @Override + void setReadOnly(boolean readOnly) { + editText.setEnabled(!readOnly); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/forms/FormWrapper.java b/src/main/java/eu/siacs/conversations/ui/forms/FormWrapper.java new file mode 100644 index 000000000..eafe95cc8 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/forms/FormWrapper.java @@ -0,0 +1,72 @@ +package eu.siacs.conversations.ui.forms; + +import android.content.Context; +import android.widget.LinearLayout; + +import java.util.ArrayList; +import java.util.List; + +import eu.siacs.conversations.xmpp.forms.Data; +import eu.siacs.conversations.xmpp.forms.Field; + +public class FormWrapper { + + private final LinearLayout layout; + + private final Data form; + + private final List<FormFieldWrapper> fieldWrappers = new ArrayList<>(); + + private FormWrapper(Context context, LinearLayout linearLayout, Data form) { + this.form = form; + this.layout = linearLayout; + this.layout.removeAllViews(); + for(Field field : form.getFields()) { + FormFieldWrapper fieldWrapper = FormFieldFactory.createFromField(context,field); + if (fieldWrapper != null) { + layout.addView(fieldWrapper.getView()); + fieldWrappers.add(fieldWrapper); + } + } + } + + public Data submit() { + for(FormFieldWrapper fieldWrapper : fieldWrappers) { + fieldWrapper.submit(); + } + this.form.submit(); + return this.form; + } + + public boolean validates() { + boolean validates = true; + for(FormFieldWrapper fieldWrapper : fieldWrappers) { + validates &= fieldWrapper.validates(); + } + return validates; + } + + public void setOnFormFieldValuesEditedListener(FormFieldWrapper.OnFormFieldValuesEdited listener) { + for(FormFieldWrapper fieldWrapper : fieldWrappers) { + fieldWrapper.setOnFormFieldValuesEditedListener(listener); + } + } + + public void setReadOnly(boolean b) { + for(FormFieldWrapper fieldWrapper : fieldWrappers) { + fieldWrapper.setReadOnly(b); + } + } + + public boolean edited() { + boolean edited = false; + for(FormFieldWrapper fieldWrapper : fieldWrappers) { + edited |= fieldWrapper.edited(); + } + return edited; + } + + public static FormWrapper createInLayout(Context context, LinearLayout layout, Data form) { + return new FormWrapper(context, layout, form); + } +} diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index d957a60d8..cc2579a9a 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -243,6 +243,7 @@ public class XmppConnection implements Runnable { tagWriter = new TagWriter(); this.changeStatus(Account.State.CONNECTING); final boolean useTor = mXmppConnectionService.useTorToConnect() || account.isOnion(); + final boolean extended = mXmppConnectionService.showExtendedConnectionOptions(); if (useTor) { String destination; if (account.getHostname() == null || account.getHostname().isEmpty()) { @@ -250,8 +251,16 @@ public class XmppConnection implements Runnable { } else { destination = account.getHostname(); } - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": connect to "+destination+" via TOR"); - socket = SocksSocketFactory.createSocketOverTor(destination,account.getPort()); + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": connect to " + destination + " via TOR"); + socket = SocksSocketFactory.createSocketOverTor(destination, account.getPort()); + startXmpp(); + } else if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) { + socket = new Socket(); + try { + socket.connect(new InetSocketAddress(account.getHostname(), account.getPort()), Config.SOCKET_TIMEOUT * 1000); + } catch (IOException e) { + throw new UnknownHostException(); + } startXmpp(); } else if (DNSHelper.isIp(account.getServer().toString())) { socket = new Socket(); @@ -540,7 +549,7 @@ public class XmppConnection implements Runnable { } else if (nextTag.isStart("failed")) { tagReader.readElement(nextTag); Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": resumption failed"); - streamId = null; + resetStreamId(); if (account.getStatus() != Account.State.ONLINE) { sendBindRequest(); } @@ -879,6 +888,7 @@ public class XmppConnection implements Runnable { public void resetEverything() { resetStreamId(); clearIqCallbacks(); + mStanzaQueue.clear(); synchronized (this.disco) { disco.clear(); } @@ -1280,7 +1290,6 @@ public class XmppConnection implements Runnable { } return; } else { - resetStreamId(); if (tagWriter.isActive()) { tagWriter.finish(); try { diff --git a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java index d05c9abb3..0053a399c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java +++ b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java @@ -53,16 +53,16 @@ public class Data extends Element { public void submit() { this.setAttribute("type","submit"); - removeNonFieldChildren(); + removeUnnecessaryChildren(); for(Field field : getFields()) { field.removeNonValueChildren(); } } - private void removeNonFieldChildren() { + private void removeUnnecessaryChildren() { for(Iterator<Element> iterator = this.children.iterator(); iterator.hasNext();) { Element element = iterator.next(); - if (!element.getName().equals("field")) { + if (!element.getName().equals("field") && !element.getName().equals("title")) { iterator.remove(); } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/forms/Field.java b/src/main/java/eu/siacs/conversations/xmpp/forms/Field.java index c1fc808dc..020b34b90 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/forms/Field.java +++ b/src/main/java/eu/siacs/conversations/xmpp/forms/Field.java @@ -1,7 +1,9 @@ package eu.siacs.conversations.xmpp.forms; +import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; +import java.util.List; import eu.siacs.conversations.xml.Element; @@ -52,6 +54,19 @@ public class Field extends Element { return findChildContent("value"); } + public List<String> getValues() { + List<String> values = new ArrayList<>(); + for(Element child : getChildren()) { + if ("value".equals(child.getName())) { + String content = child.getContent(); + if (content != null) { + values.add(content); + } + } + } + return values; + } + public String getLabel() { return getAttribute("label"); } |