automatically receive Quicksy SMS. fixes #3962

Daniel Gultsch created

requires new version of QuicksyServer

Change summary

.travis.yml                                                                           |  3 
build.gradle                                                                          | 14 
src/conversations/java/eu/siacs/conversations/services/QuickConversationsService.java | 10 
src/main/AndroidManifest.xml                                                          |  4 
src/main/java/eu/siacs/conversations/services/AbstractQuickConversationsService.java  |  7 
src/main/java/eu/siacs/conversations/services/EventReceiver.java                      |  2 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java              | 14 
src/quicksy/java/eu/siacs/conversations/services/QuickConversationsService.java       | 30 
src/quicksy/java/eu/siacs/conversations/ui/VerifyActivity.java                        |  7 
src/quicksyFree/java/eu/siacs/conversations/utils/SmsRetrieverWrapper.java            | 16 
src/quicksyPlaystore/java/eu/siacs/conversations/utils/SmsRetrieverWrapper.java       | 40 
11 files changed, 137 insertions(+), 10 deletions(-)

Detailed changes

.travis.yml 🔗

@@ -13,8 +13,7 @@ before_script:
     - mkdir libs
     - wget -O libs/libwebrtc-m87.aar https://gultsch.de/files/libwebrtc-m87.aar
 script:
-    - ./gradlew assembleConversationsFreeSystemRelease
-    - ./gradlew assembleQuicksyFreeCompatRelease
+    - ./gradlew assembleDebug
 
 before_install:
     - yes | sdkmanager "platforms;android-28"

build.gradle 🔗

@@ -26,6 +26,8 @@ configurations {
     conversationsFreeCompatImplementation
     conversationsPlaystoreCompatImplementation
     conversationsPlaystoreSystemImplementation
+    quicksyPlaystoreCompatImplementation
+    quicksyPlaystoreSystemImplementation
     quicksyFreeCompatImplementation
     quicksyImplementation
 }
@@ -41,6 +43,8 @@ dependencies {
     }
     conversationsPlaystoreCompatImplementation("com.android.installreferrer:installreferrer:2.2")
     conversationsPlaystoreSystemImplementation("com.android.installreferrer:installreferrer:2.2")
+    quicksyPlaystoreCompatImplementation 'com.google.android.gms:play-services-auth-api-phone:17.0.0'
+    quicksyPlaystoreSystemImplementation 'com.google.android.gms:play-services-auth-api-phone:17.0.0'
     implementation 'org.sufficientlysecure:openpgp-api:10.0'
     implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0'
     implementation 'androidx.appcompat:appcompat:1.2.0'
@@ -156,14 +160,21 @@ android {
     }
 
     sourceSets {
+        quicksyFreeSystem {
+            java {
+                srcDir 'src/quicksyFree/java'
+            }
+        }
         quicksyFreeCompat {
             java {
                 srcDir 'src/freeCompat/java'
+                srcDir 'src/quicksyFree/java'
             }
         }
         quicksyPlaystoreCompat {
             java {
                 srcDir 'src/playstoreCompat/java'
+                srcDir 'src/quicksyPlaystore/java'
             }
             res {
                 srcDir 'src/playstoreCompat/res'
@@ -171,6 +182,9 @@ android {
             }
         }
         quicksyPlaystoreSystem {
+            java {
+                srcDir 'src/quicksyPlaystore/java'
+            }
             res {
                 srcDir 'src/quicksyPlaystore/res'
             }

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

@@ -1,5 +1,10 @@
 package eu.siacs.conversations.services;
 
+import android.content.Intent;
+import android.util.Log;
+
+import eu.siacs.conversations.Config;
+
 public class QuickConversationsService extends AbstractQuickConversationsService {
 
     QuickConversationsService(XmppConnectionService xmppConnectionService) {
@@ -25,4 +30,9 @@ public class QuickConversationsService extends AbstractQuickConversationsService
     public void considerSyncBackground(boolean force) {
 
     }
+
+    @Override
+    public void handleSmsReceived(Intent intent) {
+        Log.d(Config.LOGTAG,"ignoring received SMS");
+    }
 }

src/main/AndroidManifest.xml 🔗

@@ -72,12 +72,14 @@
 
         <service android:name=".services.XmppConnectionService" />
 
-        <receiver android:name=".services.EventReceiver">
+        <receiver android:name=".services.EventReceiver"
+            android:permission="com.google.android.gms.auth.api.phone.permission.SEND">
             <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED" />
                 <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
                 <action android:name="android.intent.action.ACTION_SHUTDOWN" />
                 <action android:name="android.media.RINGER_MODE_CHANGED" />
+                <action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED"/>
             </intent-filter>
         </receiver>
 

src/main/java/eu/siacs/conversations/services/AbstractQuickConversationsService.java 🔗

@@ -1,9 +1,14 @@
 package eu.siacs.conversations.services;
 
+import android.content.Intent;
+
 import eu.siacs.conversations.BuildConfig;
 
 public abstract class AbstractQuickConversationsService {
 
+
+    public static final String SMS_RETRIEVED_ACTION = "com.google.android.gms.auth.api.phone.SMS_RETRIEVED";
+
     protected final XmppConnectionService service;
 
     public AbstractQuickConversationsService(XmppConnectionService service) {
@@ -25,4 +30,6 @@ public abstract class AbstractQuickConversationsService {
     public abstract boolean isSynchronizing();
 
     public abstract void considerSyncBackground(boolean force);
+
+    public abstract void handleSmsReceived(Intent intent);
 }

src/main/java/eu/siacs/conversations/services/EventReceiver.java 🔗

@@ -27,7 +27,7 @@ public class EventReceiver extends BroadcastReceiver {
         if (extras != null) {
             intentForService.putExtras(extras);
         }
-        if ("ui".equals(action) || hasEnabledAccounts(context)) {
+        if ("ui".equals(action) || QuickConversationsService.SMS_RETRIEVED_ACTION.equals(action)  || hasEnabledAccounts(context)) {
             Compatibility.startService(context, intentForService);
         } else {
             Log.d(Config.LOGTAG, "EventReceiver ignored action " + intentForService.getAction());

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -32,10 +32,6 @@ import android.os.SystemClock;
 import android.preference.PreferenceManager;
 import android.provider.ContactsContract;
 import android.security.KeyChain;
-import androidx.annotation.BoolRes;
-import androidx.annotation.IntegerRes;
-import androidx.core.app.RemoteInput;
-import androidx.core.content.ContextCompat;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -44,6 +40,11 @@ import android.util.Log;
 import android.util.LruCache;
 import android.util.Pair;
 
+import androidx.annotation.BoolRes;
+import androidx.annotation.IntegerRes;
+import androidx.core.app.RemoteInput;
+import androidx.core.content.ContextCompat;
+
 import com.google.common.base.Objects;
 import com.google.common.base.Strings;
 
@@ -135,6 +136,7 @@ import eu.siacs.conversations.utils.WakeLockHelper;
 import eu.siacs.conversations.utils.XmppUri;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xml.Namespace;
+import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.OnBindListener;
 import eu.siacs.conversations.xmpp.OnContactStatusChanged;
 import eu.siacs.conversations.xmpp.OnIqPacketReceived;
@@ -159,7 +161,6 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
 import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
 import me.leolin.shortcutbadger.ShortcutBadger;
-import eu.siacs.conversations.xmpp.Jid;
 
 public class XmppConnectionService extends Service {
 
@@ -633,6 +634,9 @@ public class XmppConnectionService extends Service {
         if (action != null) {
             final String uuid = intent.getStringExtra("uuid");
             switch (action) {
+                case QuickConversationsService.SMS_RETRIEVED_ACTION:
+                    mQuickConversationsService.handleSmsReceived(intent);
+                break;
                 case ConnectivityManager.CONNECTIVITY_ACTION:
                     if (hasInternetConnection()) {
                         if (Config.POST_CONNECTIVITY_CHANGE_PING_INTERVAL > 0) {

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

@@ -1,8 +1,10 @@
 package eu.siacs.conversations.services;
 
 
+import android.content.Intent;
 import android.content.SharedPreferences;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.SystemClock;
 import android.preference.PreferenceManager;
 import android.util.Log;
@@ -47,6 +49,7 @@ import eu.siacs.conversations.utils.AccountUtils;
 import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
 import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
+import eu.siacs.conversations.utils.SmsRetrieverWrapper;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xml.Namespace;
 import eu.siacs.conversations.xmpp.Jid;
@@ -122,6 +125,7 @@ public class QuickConversationsService extends AbstractQuickConversationsService
     public void requestVerification(Phonenumber.PhoneNumber phoneNumber) {
         final String e164 = PhoneNumberUtilWrapper.normalize(service, phoneNumber);
         if (mVerificationRequestInProgress.compareAndSet(false, true)) {
+            SmsRetrieverWrapper.start(service);
             new Thread(() -> {
                 try {
                     final URL url = new URL(BASE_URL + "/authentication/" + e164);
@@ -322,6 +326,28 @@ public class QuickConversationsService extends AbstractQuickConversationsService
         });
     }
 
+    @Override
+    public void handleSmsReceived(final Intent intent) {
+        final Bundle extras = intent.getExtras();
+        final String pin = SmsRetrieverWrapper.extractPin(extras);
+        if (pin == null) {
+            Log.d(Config.LOGTAG, "unable to extract Pin from received SMS");
+            return;
+        }
+        final Account account = AccountUtils.getFirst(service);
+        if (account == null) {
+            Log.d(Config.LOGTAG, "no account configured to process PIN received by SMS");
+            return;
+        }
+        verify(account, pin);
+        synchronized (mOnVerification) {
+            for (OnVerification onVerification : mOnVerification) {
+                onVerification.startBackgroundVerification(pin);
+            }
+        }
+
+    }
+
 
     private void considerSync(boolean forced) {
         Map<String, PhoneNumberContact> contacts = PhoneNumberContact.load(service);
@@ -429,11 +455,13 @@ public class QuickConversationsService extends AbstractQuickConversationsService
         void onVerificationSucceeded();
 
         void onVerificationRetryAt(long timestamp);
+
+        void startBackgroundVerification(String pin);
     }
 
     private static class Attempt {
         private final long timestamp;
-        private int hash;
+        private final int hash;
 
         private static final Attempt NULL = new Attempt(0, 0);
 

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

@@ -316,6 +316,12 @@ public class VerifyActivity extends XmppActivity implements ClipboardManager.OnP
         runOnUiThread(VERIFICATION_TIMEOUT_UPDATER);
     }
 
+    @Override
+    public void startBackgroundVerification(String pin) {
+        pinEntryWrapper.setPin(pin);
+        setVerifyingState(true);
+    }
+
     //send sms again button callback
     @Override
     public void onVerificationRequestFailed(int code) {
@@ -329,6 +335,7 @@ public class VerifyActivity extends XmppActivity implements ClipboardManager.OnP
     @Override
     public void onVerificationRequested() {
         runOnUiThread(() -> {
+            pinEntryWrapper.clear();
             setRequestingVerificationState(false);
             AlertDialog.Builder builder = new AlertDialog.Builder(this);
             builder.setMessage(R.string.we_have_sent_you_another_sms);

src/quicksyFree/java/eu/siacs/conversations/utils/SmsRetrieverWrapper.java 🔗

@@ -0,0 +1,16 @@
+package eu.siacs.conversations.utils;
+
+import android.os.Bundle;
+
+import eu.siacs.conversations.services.XmppConnectionService;
+
+public class SmsRetrieverWrapper {
+
+    public static void start(XmppConnectionService service) {
+        //nop
+    }
+
+    public static String extractPin(Bundle extras) {
+        return null;
+    }
+}

src/quicksyPlaystore/java/eu/siacs/conversations/utils/SmsRetrieverWrapper.java 🔗

@@ -0,0 +1,40 @@
+package eu.siacs.conversations.utils;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.google.android.gms.auth.api.phone.SmsRetriever;
+import com.google.android.gms.auth.api.phone.SmsRetrieverClient;
+import com.google.android.gms.common.api.CommonStatusCodes;
+import com.google.android.gms.common.api.Status;
+import com.google.android.gms.tasks.Task;
+import com.google.common.base.Strings;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import eu.siacs.conversations.Config;
+
+public class SmsRetrieverWrapper {
+
+    public static void start(final Context context) {
+        final SmsRetrieverClient client = SmsRetriever.getClient(context);
+        final Task<Void> task = client.startSmsRetriever();
+        task.addOnSuccessListener(aVoid -> Log.d(Config.LOGTAG, "successfully started SMS retriever"));
+        task.addOnFailureListener(e -> Log.d(Config.LOGTAG, "unable to start SMS retriever", e));
+    }
+
+    public static String extractPin(Bundle extras) {
+        final Status status = extras == null ? null : (Status) extras.get(SmsRetriever.EXTRA_STATUS);
+        if (status != null && status.getStatusCode() == CommonStatusCodes.SUCCESS) {
+            Log.d(Config.LOGTAG, "Verification SMS received with status success");
+            final String message = extras.getString(SmsRetriever.EXTRA_SMS_MESSAGE);
+            final Matcher m = Pattern.compile("(?<!\\d)\\d{6}(?!\\d)").matcher(Strings.nullToEmpty(message));
+            if (m.find()) {
+                return m.group();
+            }
+        }
+        return null;
+    }
+}