create api calls

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/crypto/sasl/Plain.java                   |   8 
src/main/res/values/strings.xml                                               |   5 
src/quick/java/eu/siacs/conversations/services/QuickConversationsService.java | 175 
src/quick/java/eu/siacs/conversations/ui/EnterPhoneNumberActivity.java        |  32 
src/quick/java/eu/siacs/conversations/ui/VerifyActivity.java                  | 109 
src/quick/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java       |  10 
src/quick/res/layout/activity_verify.xml                                      |   2 
7 files changed, 317 insertions(+), 24 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/crypto/sasl/Plain.java 🔗

@@ -24,7 +24,11 @@ public class Plain extends SaslMechanism {
 
 	@Override
 	public String getClientFirstMessage() {
-		final String sasl = '\u0000' + account.getUsername() + '\u0000' + account.getPassword();
-		return Base64.encodeToString(sasl.getBytes(Charset.defaultCharset()), Base64.NO_WRAP);
+		return getMessage(account.getUsername(), account.getPassword());
+	}
+
+	public static String getMessage(String username, String password) {
+		final String message = '\u0000' + username + '\u0000' + password;
+		return Base64.encodeToString(message.getBytes(Charset.defaultCharset()), Base64.NO_WRAP);
 	}
 }

src/main/res/values/strings.xml 🔗

@@ -760,9 +760,11 @@
     <string name="please_enter_your_phone_number">Please enter your phone number.</string>
     <string name="search_countries">Search countries</string>
     <string name="verify_x">Verify %s</string>
-    <string name="we_have_sent_you_an_sms"><![CDATA[We have sent you an SMS to <b>%s</b>.]]></string>
+    <string name="we_have_sent_you_an_sms_to_x"><![CDATA[We have sent you an SMS to <b>%s</b>.]]></string>
+    <string name="we_have_sent_you_the_sms_again">We have sent you the SMS again.</string>
     <string name="please_enter_pin_below">Please enter the 6 digit pin below.</string>
     <string name="resend_sms">Resend SMS</string>
+    <string name="resend_sms_in">Resend SMS (%s)</string>
     <string name="back">back</string>
     <string name="possible_pin">Automatically pasted possible pin from clipboard.</string>
     <string name="please_enter_pin">Please enter your 6 digit pin.</string>
@@ -770,4 +772,5 @@
     <string name="yes">Yes</string>
     <string name="no">No</string>
     <string name="verifying">Verifying…</string>
+    <string name="requesting_sms">Requesting SMS…</string>
 </resources>

src/quick/java/eu/siacs/conversations/services/QuickConversationsService.java 🔗

@@ -1,8 +1,17 @@
 package eu.siacs.conversations.services;
 
 
+import android.os.SystemClock;
 import android.util.Log;
 
+import java.io.BufferedWriter;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.net.ConnectException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
 import java.security.SecureRandom;
 import java.util.Collections;
 import java.util.Set;
@@ -10,7 +19,9 @@ import java.util.WeakHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import eu.siacs.conversations.Config;
+import eu.siacs.conversations.crypto.sasl.Plain;
 import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.utils.AccountUtils;
 import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
 import io.michaelrocks.libphonenumber.android.Phonenumber;
@@ -18,12 +29,20 @@ import rocks.xmpp.addr.Jid;
 
 public class QuickConversationsService {
 
+
+    public static final int API_ERROR_OTHER = -1;
+    public static final int API_ERROR_UNKNOWN_HOST = -2;
+    public static final int API_ERROR_CONNECT = -3;
+
+    private static final String BASE_URL = "https://venus.fritz.box:4567";
+
     private final XmppConnectionService service;
 
     private final Set<OnVerificationRequested> mOnVerificationRequested = Collections.newSetFromMap(new WeakHashMap<>());
     private final Set<OnVerification> mOnVerification = Collections.newSetFromMap(new WeakHashMap<>());
 
     private final AtomicBoolean mVerificationInProgress = new AtomicBoolean(false);
+    private final AtomicBoolean mVerificationRequestInProgress = new AtomicBoolean(false);
 
     QuickConversationsService(XmppConnectionService xmppConnectionService) {
         this.service = xmppConnectionService;
@@ -54,31 +73,136 @@ public class QuickConversationsService {
     }
 
     public void requestVerification(Phonenumber.PhoneNumber phoneNumber) {
+        final String e164 = PhoneNumberUtilWrapper.normalize(service, phoneNumber);
+
+        /**
+         * GET /authentication/+phoneNumber
+         *
+         * - returns too many requests, (sms ist unterwegs), retry after seconden -- auf jeden fall in nächste activity (verify activity) weiter leiten weil es sein kann das sms noch ankommt
+         * - returns OK; success (auch in activity weiter lassen. aber ohne error paramater; dh send again button is activ; und vielleicht kurzer toast bzw snackbar
+         * - returns invalid request user error wenn die phone number falsch ist
+         */
+        if (mVerificationRequestInProgress.compareAndSet(false, true)) {
+            new Thread(() -> {
+                try {
+
+                    Thread.sleep(5000);
+
+                    final URL url = new URL(BASE_URL + "/authentication/" + e164);
+                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+                    connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000);
+                    connection.setReadTimeout(Config.SOCKET_TIMEOUT * 1000);
+                    final int code = connection.getResponseCode();
+                    if (code == 200) {
+                        createAccountAndWait(phoneNumber, 0L);
+                    } else if (code == 429) {
+                        createAccountAndWait(phoneNumber, retryAfter(connection));
+                    } else {
+                        synchronized (mOnVerificationRequested) {
+                            for (OnVerificationRequested onVerificationRequested : mOnVerificationRequested) {
+                                onVerificationRequested.onVerificationRequestFailed(code);
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    final int code = getApiErrorCode(e);
+                    synchronized (mOnVerificationRequested) {
+                        for (OnVerificationRequested onVerificationRequested : mOnVerificationRequested) {
+                            onVerificationRequested.onVerificationRequestFailed(code);
+                        }
+                    }
+                } finally {
+                    mVerificationRequestInProgress.set(false);
+                }
+            }).start();
+        }
+
+
+    }
+
+    private void createAccountAndWait(Phonenumber.PhoneNumber phoneNumber, final long timestamp) {
         String local = PhoneNumberUtilWrapper.normalize(service, phoneNumber);
-        Log.d(Config.LOGTAG,"requesting verification for "+PhoneNumberUtilWrapper.normalize(service,phoneNumber));
-        Account account = new Account(Jid.of(local,"quick.conversations.im",null), CryptoHelper.createPassword(new SecureRandom()));
-        account.setOption(Account.OPTION_DISABLED, true);
-        account.setOption(Account.OPTION_UNVERIFIED, true);
-        service.createAccount(account);
+        Log.d(Config.LOGTAG, "requesting verification for " + PhoneNumberUtilWrapper.normalize(service, phoneNumber));
+        Jid jid = Jid.of(local, "quick.conversations.im", null);
+        Account account = AccountUtils.getFirst(service);
+        if (account == null || !account.getJid().asBareJid().equals(jid.asBareJid())) {
+            if (account != null) {
+                service.deleteAccount(account);
+            }
+            account = new Account(jid, CryptoHelper.createPassword(new SecureRandom()));
+            account.setOption(Account.OPTION_DISABLED, true);
+            account.setOption(Account.OPTION_UNVERIFIED, true);
+            service.createAccount(account);
+        }
         synchronized (mOnVerificationRequested) {
-            for(OnVerificationRequested onVerificationRequested : mOnVerificationRequested) {
-                onVerificationRequested.onVerificationRequested();
+            for (OnVerificationRequested onVerificationRequested : mOnVerificationRequested) {
+                if (timestamp <= 0) {
+                    onVerificationRequested.onVerificationRequested();
+                } else {
+                    onVerificationRequested.onVerificationRequestedRetryAt(timestamp);
+                }
             }
         }
     }
 
     public void verify(Account account, String pin) {
+        /**
+         * POST /password
+         * authentication gesetzt mit telephone nummber und verification code
+         * body = password
+         *
+         * retry after, too many requests
+         * code wrong
+         * OK (weiterleiten auf publish avatar activity
+         *
+         */
         if (mVerificationInProgress.compareAndSet(false, true)) {
             new Thread(() -> {
                 try {
+
                     Thread.sleep(5000);
+
+                    final URL url = new URL(BASE_URL + "/password");
+                    final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+                    connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000);
+                    connection.setReadTimeout(Config.SOCKET_TIMEOUT * 1000);
+                    connection.setRequestMethod("POST");
+                    connection.setRequestProperty("Authorization", Plain.getMessage(account.getUsername(), pin));
+                    final OutputStream os = connection.getOutputStream();
+                    final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
+                    writer.write(account.getPassword());
+                    writer.flush();
+                    writer.close();
+                    os.close();
+                    connection.connect();
+                    final int code = connection.getResponseCode();
+                    if (code == 200) {
+                        synchronized (mOnVerification) {
+                            for (OnVerification onVerification : mOnVerification) {
+                                onVerification.onVerificationSucceeded();
+                            }
+                        }
+                    } else if (code == 429) {
+                        final long retryAfter = retryAfter(connection);
+                        synchronized (mOnVerification) {
+                            for (OnVerification onVerification : mOnVerification) {
+                                onVerification.onVerificationRetryAt(retryAfter);
+                            }
+                        }
+                    } else {
+                        synchronized (mOnVerification) {
+                            for (OnVerification onVerification : mOnVerification) {
+                                onVerification.onVerificationFailed(code);
+                            }
+                        }
+                    }
+                } catch (Exception e) {
+                    final int code = getApiErrorCode(e);
                     synchronized (mOnVerification) {
                         for (OnVerification onVerification : mOnVerification) {
-                            onVerification.onVerificationFailed();
+                            onVerification.onVerificationFailed(code);
                         }
                     }
-                } catch (InterruptedException e) {
-                    e.printStackTrace();
                 } finally {
                     mVerificationInProgress.set(false);
                 }
@@ -86,17 +210,46 @@ public class QuickConversationsService {
         }
     }
 
+    private static int getApiErrorCode(Exception e) {
+        if (e instanceof UnknownHostException) {
+            return API_ERROR_UNKNOWN_HOST;
+        } else if (e instanceof ConnectException) {
+            return API_ERROR_CONNECT;
+        } else {
+            Log.d(Config.LOGTAG,e.getClass().getName());
+            return API_ERROR_OTHER;
+        }
+    }
+
+    private static long retryAfter(HttpURLConnection connection) {
+        try {
+            return SystemClock.elapsedRealtime() + (Long.parseLong(connection.getHeaderField("Retry-After")) * 1000L);
+        } catch (Exception e) {
+            return 0;
+        }
+    }
+
     public boolean isVerifying() {
         return mVerificationInProgress.get();
     }
 
+    public boolean isRequestingVerification() {
+        return mVerificationRequestInProgress.get();
+    }
+
     public interface OnVerificationRequested {
         void onVerificationRequestFailed(int code);
+
         void onVerificationRequested();
+
+        void onVerificationRequestedRetryAt(long timestamp);
     }
 
     public interface OnVerification {
-        void onVerificationFailed();
+        void onVerificationFailed(int code);
+
         void onVerificationSucceeded();
+
+        void onVerificationRetryAt(long timestamp);
     }
 }

src/quick/java/eu/siacs/conversations/ui/EnterPhoneNumberActivity.java 🔗

@@ -30,7 +30,10 @@ public class EnterPhoneNumberActivity extends XmppActivity implements QuickConve
     private static final int REQUEST_CHOOSE_COUNTRY = 0x1234;
 
     private ActivityEnterNumberBinding binding;
+
     private String region = null;
+    private boolean requestingVerification = false;
+
     private final TextWatcher countryCodeTextWatcher = new TextWatcher() {
         @Override
         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@@ -78,6 +81,7 @@ public class EnterPhoneNumberActivity extends XmppActivity implements QuickConve
         super.onCreate(savedInstanceState);
 
         String region = savedInstanceState != null ? savedInstanceState.getString("region") : null;
+        boolean requestingVerification = savedInstanceState != null && savedInstanceState.getBoolean("requesting_verification", false);
         if (region != null) {
             this.region = region;
         } else {
@@ -91,6 +95,7 @@ public class EnterPhoneNumberActivity extends XmppActivity implements QuickConve
         setSupportActionBar((Toolbar) this.binding.toolbar);
         this.binding.countryCode.addTextChangedListener(this.countryCodeTextWatcher);
         this.binding.countryCode.setText(String.valueOf(PhoneNumberUtilWrapper.getInstance(this).getCountryCodeForRegion(this.region)));
+        setRequestingVerificationState(requestingVerification);
     }
 
     @Override
@@ -98,6 +103,7 @@ public class EnterPhoneNumberActivity extends XmppActivity implements QuickConve
         if (this.region != null) {
             savedInstanceState.putString("region", this.region);
         }
+        savedInstanceState.putBoolean("requesting_verification", this.requestingVerification);
         super.onSaveInstanceState(savedInstanceState);
     }
 
@@ -142,9 +148,19 @@ public class EnterPhoneNumberActivity extends XmppActivity implements QuickConve
     }
 
     private void onPhoneNumberEntered(Phonenumber.PhoneNumber phoneNumber) {
+        setRequestingVerificationState(true);
         xmppConnectionService.getQuickConversationsService().requestVerification(phoneNumber);
     }
 
+    private void setRequestingVerificationState(boolean requesting) {
+        this.requestingVerification = requesting;
+        this.binding.countryCode.setEnabled(!requesting);
+        this.binding.country.setEnabled(!requesting);
+        this.binding.number.setEnabled(!requesting);
+        this.binding.next.setEnabled(!requesting);
+        this.binding.next.setText(requesting ? R.string.requesting_sms : R.string.next);
+    }
+
     @Override
     public void onActivityResult(int requestCode, int resultCode, final Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
@@ -160,13 +176,25 @@ public class EnterPhoneNumberActivity extends XmppActivity implements QuickConve
 
     @Override
     public void onVerificationRequestFailed(int code) {
-
+        runOnUiThread(()->{
+            setRequestingVerificationState(false);
+        });
     }
 
     @Override
     public void onVerificationRequested() {
         runOnUiThread(() -> {
-            startActivity(new Intent(this,VerifyActivity.class));
+            startActivity(new Intent(this, VerifyActivity.class));
+            finish();
+        });
+    }
+
+    @Override
+    public void onVerificationRequestedRetryAt(long timestamp) {
+        runOnUiThread(() -> {
+            Intent intent = new Intent(this, VerifyActivity.class);
+            intent.putExtra(VerifyActivity.EXTRA_RETRY_SMS_AFTER, timestamp);
+            startActivity(intent);
             finish();
         });
     }

src/quick/java/eu/siacs/conversations/ui/VerifyActivity.java 🔗

@@ -8,11 +8,15 @@ import android.content.Context;
 import android.content.Intent;
 import android.databinding.DataBindingUtil;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
 import android.support.design.widget.Snackbar;
 import android.support.v7.widget.Toolbar;
 import android.text.Html;
+import android.util.Log;
 import android.view.View;
 
+import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.databinding.ActivityVerifyBinding;
 import eu.siacs.conversations.entities.Account;
@@ -20,10 +24,14 @@ import eu.siacs.conversations.services.QuickConversationsService;
 import eu.siacs.conversations.ui.util.PinEntryWrapper;
 import eu.siacs.conversations.utils.AccountUtils;
 import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
+import eu.siacs.conversations.utils.TimeframeUtils;
+import io.michaelrocks.libphonenumber.android.NumberParseException;
 
 import static android.content.ClipDescription.MIMETYPE_TEXT_PLAIN;
 
-public class VerifyActivity extends XmppActivity implements ClipboardManager.OnPrimaryClipChangedListener, QuickConversationsService.OnVerification {
+public class VerifyActivity extends XmppActivity implements ClipboardManager.OnPrimaryClipChangedListener, QuickConversationsService.OnVerification, QuickConversationsService.OnVerificationRequested {
+
+    public static final String EXTRA_RETRY_SMS_AFTER = "retry_sms_after";
 
     private ActivityVerifyBinding binding;
     private Account account;
@@ -31,14 +39,43 @@ public class VerifyActivity extends XmppActivity implements ClipboardManager.OnP
     private ClipboardManager clipboardManager;
     private String pasted = null;
     private boolean verifying = false;
+    private boolean requestingVerification = false;
+    private long retrySmsAfter = 0;
+
+    private final Handler mHandler = new Handler();
+
+
+    private final Runnable SMS_TIMEOUT_UPDATER = new Runnable() {
+        @Override
+        public void run() {
+            if (setTimeoutLabelInResendButton()) {
+                mHandler.postDelayed(this,300);
+            }
+        }
+    };
 
+    private boolean setTimeoutLabelInResendButton() {
+        if (retrySmsAfter != 0) {
+            long remaining = retrySmsAfter - SystemClock.elapsedRealtime();
+            if (remaining >= 0) {
+                binding.resendSms.setEnabled(false);
+                binding.resendSms.setText(getString(R.string.resend_sms_in, TimeframeUtils.resolve(VerifyActivity.this,remaining)));
+                return true;
+            }
+        }
+        binding.resendSms.setEnabled(true);
+        binding.resendSms.setText(R.string.resend_sms);
+        return false;
+    }
 
     @Override
     protected void onCreate(final Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         String pin = savedInstanceState != null ? savedInstanceState.getString("pin") : null;
         boolean verifying = savedInstanceState != null && savedInstanceState.getBoolean("verifying");
+        boolean requestingVerification = savedInstanceState != null && savedInstanceState.getBoolean("requesting_verification", false);
         this.pasted = savedInstanceState != null ? savedInstanceState.getString("pasted") : null;
+        this.retrySmsAfter = savedInstanceState != null ? savedInstanceState.getLong(EXTRA_RETRY_SMS_AFTER,0L) : 0L;
         this.binding = DataBindingUtil.setContentView(this, R.layout.activity_verify);
         setSupportActionBar((Toolbar) this.binding.toolbar);
         this.pinEntryWrapper = new PinEntryWrapper(binding.pinBox);
@@ -47,8 +84,10 @@ public class VerifyActivity extends XmppActivity implements ClipboardManager.OnP
         }
         binding.back.setOnClickListener(this::onBackButton);
         binding.next.setOnClickListener(this::onNextButton);
+        binding.resendSms.setOnClickListener(this::onResendSmsButton);
         clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
         setVerifyingState(verifying);
+        setRequestingVerificationState(requestingVerification);
     }
 
     private void onBackButton(View view) {
@@ -88,6 +127,15 @@ public class VerifyActivity extends XmppActivity implements ClipboardManager.OnP
         }
     }
 
+    private void onResendSmsButton(View view) {
+        try {
+            xmppConnectionService.getQuickConversationsService().requestVerification(PhoneNumberUtilWrapper.toPhoneNumber(this, account.getJid()));
+            setRequestingVerificationState(true);
+        } catch (NumberParseException e) {
+
+        }
+    }
+
     private void setVerifyingState(boolean verifying) {
         this.verifying = verifying;
         this.binding.back.setText(verifying ? R.string.cancel : R.string.back);
@@ -99,6 +147,17 @@ public class VerifyActivity extends XmppActivity implements ClipboardManager.OnP
         this.binding.progressBar.setIndeterminate(verifying);
     }
 
+    private void setRequestingVerificationState(boolean requesting) {
+        this.requestingVerification = requesting;
+        if (requesting) {
+            this.binding.resendSms.setEnabled(false);
+            this.binding.resendSms.setText(R.string.requesting_sms);
+        } else {
+            setTimeoutLabelInResendButton();
+        }
+
+    }
+
     @Override
     protected void refreshUiReal() {
 
@@ -107,18 +166,22 @@ public class VerifyActivity extends XmppActivity implements ClipboardManager.OnP
     @Override
     void onBackendConnected() {
         xmppConnectionService.getQuickConversationsService().addOnVerificationListener(this);
+        xmppConnectionService.getQuickConversationsService().addOnVerificationRequestedListener(this);
         this.account = AccountUtils.getFirst(xmppConnectionService);
         if (this.account == null) {
             return;
         }
-        this.binding.weHaveSent.setText(Html.fromHtml(getString(R.string.we_have_sent_you_an_sms, PhoneNumberUtilWrapper.prettyPhoneNumber(this, this.account.getJid()))));
+        this.binding.weHaveSent.setText(Html.fromHtml(getString(R.string.we_have_sent_you_an_sms_to_x, PhoneNumberUtilWrapper.toFormattedPhoneNumber(this, this.account.getJid()))));
         setVerifyingState(xmppConnectionService.getQuickConversationsService().isVerifying());
+        setRequestingVerificationState(xmppConnectionService.getQuickConversationsService().isRequestingVerification());
     }
 
     @Override
     public void onSaveInstanceState(Bundle savedInstanceState) {
         savedInstanceState.putString("pin", this.pinEntryWrapper.getPin());
         savedInstanceState.putBoolean("verifying", this.verifying);
+        savedInstanceState.putBoolean("requesting_verification", this.requestingVerification);
+        savedInstanceState.putLong(EXTRA_RETRY_SMS_AFTER, this.retrySmsAfter);
         if (this.pasted != null) {
             savedInstanceState.putString("pasted", this.pasted);
         }
@@ -129,14 +192,19 @@ public class VerifyActivity extends XmppActivity implements ClipboardManager.OnP
     public void onStart() {
         super.onStart();
         clipboardManager.addPrimaryClipChangedListener(this);
+        if (this.retrySmsAfter > 0) {
+            mHandler.post(SMS_TIMEOUT_UPDATER);
+        }
     }
 
     @Override
     public void onStop() {
         super.onStop();
+        mHandler.removeCallbacks(SMS_TIMEOUT_UPDATER);
         clipboardManager.removePrimaryClipChangedListener(this);
         if (xmppConnectionService != null) {
             xmppConnectionService.getQuickConversationsService().removeOnVerificationListener(this);
+            xmppConnectionService.getQuickConversationsService().removeOnVerificationRequestedListener(this);
         }
     }
 
@@ -174,14 +242,49 @@ public class VerifyActivity extends XmppActivity implements ClipboardManager.OnP
     }
 
     @Override
-    public void onVerificationFailed() {
+    public void onVerificationFailed(int code) {
         runOnUiThread(() -> {
             setVerifyingState(false);
         });
+        Log.d(Config.LOGTAG,"code="+code);
     }
 
     @Override
     public void onVerificationSucceeded() {
 
     }
+
+    @Override
+    public void onVerificationRetryAt(long timestamp) {
+
+    }
+
+    //send sms again button callback
+    @Override
+    public void onVerificationRequestFailed(int code) {
+        runOnUiThread(()->{
+            setRequestingVerificationState(false);
+        });
+        Log.d(Config.LOGTAG,"code="+code);
+    }
+
+    //send sms again button callback
+    @Override
+    public void onVerificationRequested() {
+        runOnUiThread(()-> {
+            setRequestingVerificationState(false);
+            AlertDialog.Builder builder = new AlertDialog.Builder(this);
+            builder.setMessage(R.string.we_have_sent_you_the_sms_again);
+            builder.setPositiveButton(R.string.ok, null);
+            builder.create().show();
+        });
+    }
+
+    @Override
+    public void onVerificationRequestedRetryAt(long timestamp) {
+        this.retrySmsAfter = timestamp;
+        runOnUiThread(()-> setRequestingVerificationState(false));
+        mHandler.removeCallbacks(SMS_TIMEOUT_UPDATER);
+        runOnUiThread(SMS_TIMEOUT_UPDATER);
+    }
 }

src/quick/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java 🔗

@@ -41,16 +41,18 @@ public class PhoneNumberUtilWrapper {
         return locale.getCountry();
     }
 
-    public static String prettyPhoneNumber(Context context, Jid jid) {
-        PhoneNumberUtil phoneNumberUtil = getInstance(context);
+    public static String toFormattedPhoneNumber(Context context, Jid jid) {
         try {
-            Phonenumber.PhoneNumber phoneNumber = phoneNumberUtil.parse(jid.getEscapedLocal(), "de");
-            return phoneNumberUtil.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL);
+            return getInstance(context).format(toPhoneNumber(context, jid), PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL);
         } catch (Exception e) {
             return jid.getEscapedLocal();
         }
     }
 
+    public static Phonenumber.PhoneNumber toPhoneNumber(Context context, Jid jid) throws NumberParseException {
+        return getInstance(context).parse(jid.getEscapedLocal(), "de");
+    }
+
     public static String normalize(Context context, String number) throws NumberParseException {
         return normalize(context, getInstance(context).parse(number, getUserCountry(context)));
     }

src/quick/res/layout/activity_verify.xml 🔗

@@ -39,7 +39,7 @@
                             android:id="@+id/we_have_sent"
                             android:layout_width="wrap_content"
                             android:layout_height="wrap_content"
-                            android:text="@string/we_have_sent_you_an_sms"
+                            android:text="@string/we_have_sent_you_an_sms_to_x"
                             android:textAppearance="@style/TextAppearance.Conversations.Subhead" />
 
                         <TextView