~singpolyma/cheogram-android

d039c4870f4ac3e48fb597690db732d96faa0d72 — Daniel Gultsch 4 years ago 387e32c
support registration via pars tokens
M src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java => src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java +116 -92
@@ 2,7 2,9 @@ package eu.siacs.conversations.ui;

import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;


@@ 15,102 17,124 @@ import java.security.SecureRandom;

import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.MagicCreateBinding;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.utils.CryptoHelper;
import rocks.xmpp.addr.Jid;

public class MagicCreateActivity extends XmppActivity implements TextWatcher {

	private TextView mFullJidDisplay;
	private EditText mUsername;

	@Override
	protected void refreshUiReal() {

	}

	@Override
	void onBackendConnected() {

	}

	@Override
	public void onStart() {
		super.onStart();
		final int theme = findTheme();
		if (this.mTheme != theme) {
			recreate();
		}
	}

	@Override
	protected void onCreate(final Bundle savedInstanceState) {
		if (getResources().getBoolean(R.bool.portrait_only)) {
			setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
		}
		super.onCreate(savedInstanceState);
		setContentView(R.layout.magic_create);
		setSupportActionBar(findViewById(R.id.toolbar));
		configureActionBar(getSupportActionBar());
		mFullJidDisplay = findViewById(R.id.full_jid);
		mUsername = findViewById(R.id.username);
		Button next = findViewById(R.id.create_account);
		next.setOnClickListener(v -> {
			try {
				String username = mUsername.getText().toString();
				Jid jid = Jid.of(username.toLowerCase(), Config.MAGIC_CREATE_DOMAIN, null);
				if (!jid.getEscapedLocal().equals(jid.getLocal())|| username.length() < 3) {
					mUsername.setError(getString(R.string.invalid_username));
					mUsername.requestFocus();
				} else {
					mUsername.setError(null);
					Account account = xmppConnectionService.findAccountByJid(jid);
					if (account == null) {
						account = new Account(jid, CryptoHelper.createPassword(new SecureRandom()));
						account.setOption(Account.OPTION_REGISTER, true);
						account.setOption(Account.OPTION_DISABLED, true);
						account.setOption(Account.OPTION_MAGIC_CREATE, true);
						xmppConnectionService.createAccount(account);
					}
					Intent intent = new Intent(MagicCreateActivity.this, EditAccountActivity.class);
					intent.putExtra("jid", account.getJid().asBareJid().toString());
					intent.putExtra("init", true);
					intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
					Toast.makeText(MagicCreateActivity.this, R.string.secure_password_generated, Toast.LENGTH_SHORT).show();
					StartConversationActivity.addInviteUri(intent, getIntent());
					startActivity(intent);
				}
			} catch (IllegalArgumentException e) {
				mUsername.setError(getString(R.string.invalid_username));
				mUsername.requestFocus();
			}
		});
		mUsername.addTextChangedListener(this);
	}

	@Override
	public void beforeTextChanged(CharSequence s, int start, int count, int after) {

	}

	@Override
	public void onTextChanged(CharSequence s, int start, int before, int count) {

	}

	@Override
	public void afterTextChanged(Editable s) {
		if (s.toString().trim().length() > 0) {
			try {
				mFullJidDisplay.setVisibility(View.VISIBLE);
				Jid jid = Jid.of(s.toString().toLowerCase(), Config.MAGIC_CREATE_DOMAIN, null);
				mFullJidDisplay.setText(getString(R.string.your_full_jid_will_be, jid.toEscapedString()));
			} catch (IllegalArgumentException e) {
				mFullJidDisplay.setVisibility(View.INVISIBLE);
			}

		} else {
			mFullJidDisplay.setVisibility(View.INVISIBLE);
		}
	}
    public static final String EXTRA_DOMAIN = "domain";
    public static final String EXTRA_PRE_AUTH = "pre_auth";

    private MagicCreateBinding binding;
    private String domain;
    private String preAuth;

    @Override
    protected void refreshUiReal() {

    }

    @Override
    void onBackendConnected() {

    }

    @Override
    public void onStart() {
        super.onStart();
        final int theme = findTheme();
        if (this.mTheme != theme) {
            recreate();
        }
    }

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        final Intent data = getIntent();
        this.domain = data == null ? null : data.getStringExtra(EXTRA_DOMAIN);
        this.preAuth = data == null ? null : data.getStringExtra(EXTRA_PRE_AUTH);
        if (getResources().getBoolean(R.bool.portrait_only)) {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        }
        super.onCreate(savedInstanceState);
        this.binding = DataBindingUtil.setContentView(this, R.layout.magic_create);
        setSupportActionBar((Toolbar) this.binding.toolbar);
        configureActionBar(getSupportActionBar(), this.domain == null);
        if (domain != null) {
            binding.instructions.setText(getString(R.string.magic_create_text_on_x, domain));
            binding.finePrint.setVisibility(View.INVISIBLE);
        }
        binding.createAccount.setOnClickListener(v -> {
            try {
                final String username = binding.username.getText().toString();
                final Jid jid;
                if (this.domain == null) {
                    jid = Jid.ofLocalAndDomain(username, Config.MAGIC_CREATE_DOMAIN);
                } else {
                    jid = Jid.ofLocalAndDomain(username, this.domain);
                }
                if (!jid.getEscapedLocal().equals(jid.getLocal()) || username.length() < 3) {
                    binding.username.setError(getString(R.string.invalid_username));
                    binding.username.requestFocus();
                } else {
                    binding.username.setError(null);
                    Account account = xmppConnectionService.findAccountByJid(jid);
                    if (account == null) {
                        account = new Account(jid, CryptoHelper.createPassword(new SecureRandom()));
                        account.setOption(Account.OPTION_REGISTER, true);
                        account.setOption(Account.OPTION_DISABLED, true);
                        account.setOption(Account.OPTION_MAGIC_CREATE, true);
                        if (this.preAuth != null) {
                            account.setKey(Account.PRE_AUTH_REGISTRATION_TOKEN, this.preAuth);
                        }
                        xmppConnectionService.createAccount(account);
                    }
                    Intent intent = new Intent(MagicCreateActivity.this, EditAccountActivity.class);
                    intent.putExtra("jid", account.getJid().asBareJid().toString());
                    intent.putExtra("init", true);
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
                    Toast.makeText(MagicCreateActivity.this, R.string.secure_password_generated, Toast.LENGTH_SHORT).show();
                    StartConversationActivity.addInviteUri(intent, getIntent());
                    startActivity(intent);
                }
            } catch (IllegalArgumentException e) {
                binding.username.setError(getString(R.string.invalid_username));
                binding.username.requestFocus();
            }
        });
        binding.username.addTextChangedListener(this);
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }

    @Override
    public void afterTextChanged(Editable s) {
        if (s.toString().trim().length() > 0) {
            try {
                binding.fullJid.setVisibility(View.VISIBLE);
                final Jid jid;
                if (this.domain == null) {
                    jid = Jid.ofLocalAndDomain(s.toString(), Config.MAGIC_CREATE_DOMAIN);
                } else {
                    jid = Jid.ofLocalAndDomain(s.toString(), this.domain);
                }
                binding.fullJid.setText(getString(R.string.your_full_jid_will_be, jid.toEscapedString()));
            } catch (IllegalArgumentException e) {
                binding.fullJid.setVisibility(View.INVISIBLE);
            }

        } else {
            binding.fullJid.setVisibility(View.INVISIBLE);
        }
    }
}

M src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java => src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java +12 -0
@@ 8,6 8,7 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.ConversationsActivity;
import eu.siacs.conversations.ui.EditAccountActivity;
import eu.siacs.conversations.ui.MagicCreateActivity;
import eu.siacs.conversations.ui.ManageAccountActivity;
import eu.siacs.conversations.ui.PickServerActivity;
import eu.siacs.conversations.ui.StartConversationActivity;


@@ 15,6 16,17 @@ import eu.siacs.conversations.ui.WelcomeActivity;

public class SignupUtils {

    public static boolean isSupportTokenRegistry() {
        return true;
    }

    public static Intent getTokenRegistrationIntent(final Activity activity, String domain, String preauth) {
        final Intent intent = new Intent(activity, MagicCreateActivity.class);
        intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, domain);
        intent.putExtra(MagicCreateActivity.EXTRA_PRE_AUTH, preauth);
        return intent;
    }

    public static Intent getSignUpIntent(final Activity activity) {
        return getSignUpIntent(activity, false);
    }

M src/conversations/res/layout/magic_create.xml => src/conversations/res/layout/magic_create.xml +94 -79
@@ 1,97 1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <include layout="@layout/toolbar" />
    <LinearLayout

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true">
        <RelativeLayout
        android:orientation="vertical">

        <include layout="@layout/toolbar" android:id="@+id/toolbar"/>

        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="?attr/color_background_primary">
            android:fillViewport="true">

            <LinearLayout
                android:id="@+id/linearLayout"
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:layout_alignParentLeft="true"
                android:layout_alignParentStart="true"
                android:minHeight="256dp"
                android:orientation="vertical"
                android:paddingBottom="10dp"
                android:paddingLeft="16dp"
                android:paddingRight="16dp">
                <Space
                android:background="?attr/color_background_primary">

                <LinearLayout
                    android:id="@+id/linearLayout"
                    android:layout_width="match_parent"
                    android:layout_height="0dp"
                    android:layout_weight="1"/>
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/pick_your_username"
                    android:textAppearance="@style/TextAppearance.Conversations.Title"/>
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:text="@string/magic_create_text"
                    android:textAppearance="@style/TextAppearance.Conversations.Body1"/>
                <EditText
                    android:id="@+id/username"
                    android:layout_alignParentStart="true"
                    android:layout_alignParentLeft="true"
                    android:layout_alignParentBottom="true"
                    android:minHeight="256dp"
                    android:orientation="vertical"
                    android:paddingLeft="16dp"
                    android:paddingRight="16dp"
                    android:paddingBottom="10dp">

                    <Space
                        android:layout_width="match_parent"
                        android:layout_height="0dp"
                        android:layout_weight="1" />

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="@string/pick_your_username"
                        android:textAppearance="@style/TextAppearance.Conversations.Title" />

                    <TextView
                        android:id="@+id/instructions"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="8dp"
                        android:text="@string/magic_create_text"
                        android:textAppearance="@style/TextAppearance.Conversations.Body1" />

                    <EditText
                        android:id="@+id/username"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center_horizontal"
                        android:hint="@string/username_hint"
                        android:inputType="textNoSuggestions" />

                    <TextView
                        android:id="@+id/full_jid"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="8dp"
                        android:text="@string/your_full_jid_will_be"
                        android:textAppearance="@style/TextAppearance.Conversations.Caption"
                        android:visibility="invisible" />

                    <Button
                        android:id="@+id/create_account"
                        style="@style/Widget.Conversations.Button.Borderless"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="right"
                        android:text="@string/next"
                        android:textColor="?colorAccent" />
                </LinearLayout>

                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center_horizontal"
                    android:hint="@string/username_hint"
                    android:inputType="textNoSuggestions"/>
                    android:layout_height="match_parent"
                    android:layout_above="@+id/linearLayout"
                    android:layout_alignParentStart="true"
                    android:layout_alignParentLeft="true">

                    <ImageView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_centerHorizontal="true"
                        android:layout_centerVertical="true"
                        android:padding="8dp"
                        android:src="@drawable/main_logo" />
                </RelativeLayout>

                <TextView
                    android:id="@+id/full_jid"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:text="@string/your_full_jid_will_be"
                    android:textAppearance="@style/TextAppearance.Conversations.Caption"
                    android:visibility="invisible"/>
                <Button
                    android:id="@+id/create_account"
                    style="@style/Widget.Conversations.Button.Borderless"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="right"
                    android:text="@string/next"
                    android:textColor="?colorAccent"/>
            </LinearLayout>
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_above="@+id/linearLayout"
                android:layout_alignParentLeft="true"
                android:layout_alignParentStart="true">
                <ImageView
                    android:id="@+id/fine_print"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentBottom="true"
                    android:layout_centerHorizontal="true"
                    android:layout_centerVertical="true"
                    android:padding="8dp"
                    android:src="@drawable/main_logo"/>
                    android:maxLines="1"
                    android:paddingLeft="8dp"
                    android:paddingRight="8dp"
                    android:text="@string/free_for_six_month"
                    android:textColor="?android:textColorSecondary"
                    android:textSize="@dimen/fineprint_size" />
            </RelativeLayout>
            <TextView
                android:paddingLeft="8dp"
                android:paddingRight="8dp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:textColor="?android:textColorSecondary"
                android:textSize="@dimen/fineprint_size"
                android:maxLines="1"
                android:text="@string/free_for_six_month"
                android:layout_centerHorizontal="true"/>
        </RelativeLayout>
    </ScrollView>
</LinearLayout>
        </ScrollView>
    </LinearLayout>
</layout>

M src/conversations/res/values/strings.xml => src/conversations/res/values/strings.xml +1 -1
@@ 5,5 5,5 @@
    <string name="create_new_account">Create new account</string>
    <string name="do_you_have_an_account">Do you already have an XMPP account? This might be the case if you are already using a different XMPP client or have used Conversations before. If not you can create a new XMPP account right now.\nHint: Some email providers also provide XMPP accounts.</string>
    <string name="server_select_text">XMPP is a provider independent instant messaging network. You can use this client with what ever XMPP server you choose.\nHowever for your convenience we made it easy to create an account on conversations.imĀ¹; a provider specially suited for the use with Conversations.</string>

    <string name="magic_create_text_on_x">You have been invited to %1$s. We will guide you through the process of creating an account.\nWhen picking %1$s as a provider you will be able to communicate with users of other providers by giving them your full XMPP address.</string>
</resources>
\ No newline at end of file

M src/main/java/eu/siacs/conversations/entities/Account.java => src/main/java/eu/siacs/conversations/entities/Account.java +4 -0
@@ 50,6 50,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable 
    public static final String RESOURCE = "resource";

    public static final String PINNED_MECHANISM_KEY = "pinned_mechanism";
    public static final String PRE_AUTH_REGISTRATION_TOKEN = "pre_auth_registration";

    public static final int OPTION_USETLS = 0;
    public static final int OPTION_DISABLED = 1;


@@ 619,6 620,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable 
        REGISTRATION_CONFLICT(true, false),
        REGISTRATION_NOT_SUPPORTED(true, false),
        REGISTRATION_PLEASE_WAIT(true, false),
        REGISTRATION_INVALID_TOKEN(true,false),
        REGISTRATION_PASSWORD_TOO_WEAK(true, false),
        TLS_ERROR,
        INCOMPATIBLE_SERVER,


@@ 683,6 685,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable 
                    return R.string.account_status_regis_success;
                case REGISTRATION_NOT_SUPPORTED:
                    return R.string.account_status_regis_not_sup;
                case REGISTRATION_INVALID_TOKEN:
                    return R.string.account_status_regis_invalid_token;
                case TLS_ERROR:
                    return R.string.account_status_tls_error;
                case INCOMPATIBLE_SERVER:

M src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java => src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +13 -0
@@ 403,6 403,16 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
            xmppConnectionService.deleteAccount(mAccount);
        }

        final boolean magicCreate = mAccount != null && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY);
        final Jid jid = mAccount == null ? null : mAccount.getJid();

        if (SignupUtils.isSupportTokenRegistry() && jid != null && magicCreate && !jid.getDomain().equals(Config.MAGIC_CREATE_DOMAIN)) {
            final Intent intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), mAccount.getKey(Account.PRE_AUTH_REGISTRATION_TOKEN));
            startActivity(intent);
            return;
        }


        if (xmppConnectionService.getAccounts().size() == 0 && Config.MAGIC_CREATE_DOMAIN != null) {
            Intent intent = SignupUtils.getSignUpIntent(this, mForceRegister != null && mForceRegister);
            startActivity(intent);


@@ 816,6 826,9 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
            return false;
        }
        switch (item.getItemId()) {
            case android.R.id.home:
                deleteAccountAndReturnIfNecessary();
                break;
            case R.id.action_show_block_list:
                final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class);
                showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());

M src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java => src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java +14 -0
@@ 9,12 9,14 @@ import android.os.Build;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.persistance.DatabaseBackend;
import eu.siacs.conversations.utils.SignupUtils;


@@ 87,6 89,18 @@ public class UriHandlerActivity extends AppCompatActivity {
        final XmppUri xmppUri = new XmppUri(uri);
        final List<Jid> accounts = DatabaseBackend.getInstance(this).getAccountJids(true);

        if (SignupUtils.isSupportTokenRegistry() && xmppUri.isJidValid() && xmppUri.isAction(XmppUri.ACTION_REGISTER)) {
            final String preauth = xmppUri.getParamater("preauth");
            final Jid jid = xmppUri.getJid();
            if (jid.isDomainJid()) {
                intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), preauth);
                startActivity(intent);
                return;
            }
            Log.d(Config.LOGTAG,"attempting to register on "+jid+" with preauth="+preauth);
            return;
        }

        if (accounts.size() == 0) {
            if (xmppUri.isJidValid()) {
                intent = SignupUtils.getSignUpIntent(this);

M src/main/java/eu/siacs/conversations/utils/XmppUri.java => src/main/java/eu/siacs/conversations/utils/XmppUri.java +5 -0
@@ 28,6 28,7 @@ public class XmppUri {

	public static final String ACTION_JOIN = "join";
	public static final String ACTION_MESSAGE = "message";
	public static final String ACTION_REGISTER = "register";

	public XmppUri(String uri) {
		try {


@@ 194,6 195,10 @@ public class XmppUri {
		return parameters.get("name");
	}

	public String getParamater(String key) {
		return this.parameters.get(key);
	}

	public List<Fingerprint> getFingerprints() {
		return this.fingerprints;
	}

M src/main/java/eu/siacs/conversations/xml/Namespace.java => src/main/java/eu/siacs/conversations/xml/Namespace.java +2 -0
@@ 37,4 37,6 @@ public final class Namespace {
	public static final String MUC_USER = "http://jabber.org/protocol/muc#user";
	public static final String BOOKMARKS2 = "urn:xmpp:bookmarks:0";
	public static final String BOOKMARKS2_COMPAT = BOOKMARKS2+"#compat";
	public static final String INVITE = "urn:xmpp:invite";
	public static final String PARS = "urn:xmpp:pars:0";
}

M src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java => src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +26 -1
@@ 112,6 112,7 @@ public class XmppConnection implements Runnable {
        public void onIqPacketReceived(Account account, IqPacket packet) {
            if (packet.getType() == IqPacket.TYPE.RESULT) {
                account.setOption(Account.OPTION_REGISTER, false);
                Log.d(Config.LOGTAG, account.getJid().asBareJid()+": successfully registered new account on server");
                throw new StateChangingError(Account.State.REGISTRATION_SUCCESSFUL);
            } else {
                final List<String> PASSWORD_TOO_WEAK_MSGS = Arrays.asList(


@@ 838,7 839,7 @@ public class XmppConnection implements Runnable {
            sendStartTLS();
        } else if (this.streamFeatures.hasChild("register") && account.isOptionSet(Account.OPTION_REGISTER)) {
            if (isSecure) {
                sendRegistryRequest();
                register();
            } else {
                Log.d(Config.LOGTAG,account.getJid().asBareJid()+": unable to find STARTTLS for registration process "+ XmlHelper.printElementNames(this.streamFeatures));
                throw new StateChangingException(Account.State.INCOMPATIBLE_SERVER);


@@ 912,6 913,26 @@ public class XmppConnection implements Runnable {
        return mechanisms;
    }


    private void register() {
        final String preAuth = account.getKey(Account.PRE_AUTH_REGISTRATION_TOKEN);
        if (preAuth != null && features.invite()) {
            final IqPacket preAuthRequest = new IqPacket(IqPacket.TYPE.SET);
            preAuthRequest.addChild("preauth", Namespace.PARS).setAttribute("token", preAuth);
            sendUnmodifiedIqPacket(preAuthRequest, (account, response) -> {
                if (response.getType() == IqPacket.TYPE.RESULT) {
                    sendRegistryRequest();
                } else {
                    final Element error = response.getError();
                    Log.d(Config.LOGTAG,account.getJid().asBareJid()+": failed to pre auth. "+error);
                    throw new StateChangingError(Account.State.REGISTRATION_INVALID_TOKEN);
                }
            }, true);
        } else {
            sendRegistryRequest();
        }
    }

    private void sendRegistryRequest() {
        final IqPacket register = new IqPacket(IqPacket.TYPE.GET);
        register.query(Namespace.REGISTER);


@@ 1776,6 1797,10 @@ public class XmppConnection implements Runnable {
            return hasDiscoFeature(Jid.of(account.getServer()), Namespace.REGISTER);
        }

        public boolean invite() {
            return connection.streamFeatures != null && connection.streamFeatures.hasChild("register", Namespace.INVITE);
        }

        public boolean sm() {
            return streamId != null
                    || (connection.streamFeatures != null && connection.streamFeatures.hasChild("sm"));

M src/main/res/values/strings.xml => src/main/res/values/strings.xml +1 -0
@@ 155,6 155,7 @@
    <string name="account_status_regis_conflict">Username already in use</string>
    <string name="account_status_regis_success">Registration completed</string>
    <string name="account_status_regis_not_sup">Server does not support registration</string>
    <string name="account_status_regis_invalid_token">Invalid registration token</string>
    <string name="account_status_tls_error">TLS negotiation failed</string>
    <string name="account_status_policy_violation">Policy violation</string>
    <string name="account_status_incompatible_server">Incompatible server</string>