Thursday, February 4, 2016

How to Add Automatic SMS OTP Verification to Android Apps (WhatsApp‑style) — Part II

In continuation with my previous blog Adding SMS Verification Like WhatsApp to Android Application Part-I, here I continue with the implementation details for automatic SMS OTP verification. This part shows the BroadcastReceiver, IntentService, layouts, manifest changes and the registration screen.

TL;DR

  • The article contains the core runnable code for: HttpService (network call to verify OTP), SmsReceiver (reads incoming SMS and extracts OTP), SmsActivity (UI to request SMS and enter OTP manually), and MainActivity (post-login screen).
  • Important: For modern Android you should read the "Android versions & security" section below before copying the code: runtime permissions, SMS Retriever/User Consent APIs and background execution limits are not covered by the original code and must be added for Android 6+ and Android 8+.

1. HttpService.java (service directory)

This IntentService receives an OTP string via intent extras and posts it to the server to verify and activate the user. The code uses Volley for network requests and PrefManager to store the user profile on success.

Code (copy into service/HttpService.java):

package marutsoft.com.smsverify.service;

import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;

import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.Map;

import marutsoft.com.smsverify.activity.MainActivity;
import marutsoft.com.smsverify.app.Config;
import marutsoft.com.smsverify.app.MyApplication;
import marutsoft.com.smsverify.helper.PrefManager;

/**
 * Created by Karjol G T on 01-02-2016.
 */
public class HttpService extends IntentService {

    private static String TAG = HttpService.class.getSimpleName();

    public HttpService() {
        super(HttpService.class.getSimpleName());
    }

    @Override    
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            String otp = intent.getStringExtra("otp");
            verifyOtp(otp);
        }
    }

    /**
     * Posting the OTP to server and activating the user
     *
     * @param otp otp received in the SMS
     */    
    private void verifyOtp(final String otp) {
        StringRequest strReq = new StringRequest(Request.Method.POST,

                Config.URL_VERIFY_OTP, new Response.Listener<String>() {

            @Override            
            public void onResponse(String response) {
                Log.d(TAG, response.toString());

                try {

                    JSONObject responseObj = new JSONObject(response);
                    Toast.makeText(getApplicationContext(), responseObj.toString(), Toast.LENGTH_LONG).show();
                    // Parsing json object response
                    // response will be a json object
                    boolean error = responseObj.getBoolean("error");
                    String message = responseObj.getString("message");

                    if (!error) {
                        // parsing the user profile information                     
                        JSONObject profileObj = responseObj.getJSONObject("profile");

                        String name = profileObj.getString("name");
                        String email = profileObj.getString("email");
                        String mobile = profileObj.getString("mobile");

                        PrefManager pref = new PrefManager(getApplicationContext());
                        pref.createLogin(name, email, mobile);

                        Intent intent = new Intent(HttpService.this, MainActivity.class);
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        startActivity(intent);

                        Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();

                    } else {
                        Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
                    }

                } catch (JSONException e) {
                    Toast.makeText(getApplicationContext(),
                            "Error: " + e.getMessage(),
                            Toast.LENGTH_LONG).show();
                }

            }
        }, new Response.ErrorListener() {

            @Override            
            public void onErrorResponse(VolleyError error) {
                Log.e(TAG, "Error: " + error.getMessage());
                Toast.makeText(getApplicationContext(),
                        error.getMessage(), Toast.LENGTH_SHORT).show();
            }
        }) {

            @Override            
            protected Map<String, String> getParams() {
                Map<String, String> params = new HashMap<String, String>();
                params.put("otp", otp);

                Log.e(TAG, "Posting params: " + params.toString());
                return params;
            }

        };

        // Adding request to request queue
        MyApplication.getInstance().addToRequestQueue(strReq);
    }

}

Note: avoid logging or Toasting the OTP in production (this code uses them for debugging). Also consider replacing IntentService with JobIntentService / WorkManager for Android O+ background restrictions (see "Android versions & security" below).


2. SmsReceiver.java (receiver directory)

This BroadcastReceiver listens for incoming SMS messages, filters by origin, extracts the OTP using a delimiter, and starts HttpService with the OTP.

Code (copy into receiver/SmsReceiver.java):

package marutsoft.com.smsverify.receiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.util.Log;

import marutsoft.com.smsverify.app.Config;
import marutsoft.com.smsverify.service.HttpService;

/**
 * Created by Karjol G T on 01-02-2016.
 */public class SmsReceiver extends BroadcastReceiver {

    private static final String TAG = SmsReceiver.class.getSimpleName();

    @Override    
    public void onReceive(Context context, Intent intent) {

        final Bundle bundle = intent.getExtras();
        try {
            if (bundle != null) {
                Object[] pdusObj = (Object[]) bundle.get("pdus");
                for (Object aPdusObj : pdusObj) {
                    SmsMessage currentMessage = SmsMessage.createFromPdu((byte[]) aPdusObj);
                    String senderAddress = currentMessage.getDisplayOriginatingAddress();
                    String message = currentMessage.getDisplayMessageBody();

                    Log.e(TAG, "Received SMS: " + message + ", Sender: " + senderAddress);

                    // if the SMS is not from our gateway, ignore the message                   
                    if (!senderAddress.toLowerCase().contains(Config.SMS_ORIGIN.toLowerCase())) {
                        return;
                    }

                    // verification code from sms                    
                    String verificationCode = getVerificationCode(message);

                    Log.e(TAG, "OTP received: " + verificationCode);

                    Intent hhtpIntent = new Intent(context, HttpService.class);
                    hhtpIntent.putExtra("otp", verificationCode);
                    context.startService(hhtpIntent);
                }
            }
        } catch (Exception e) {
            Log.e(TAG, "Exception: " + e.getMessage());
        }
    }

    /**
     * Getting the OTP from sms message body
     * ':' is the separator of OTP from the message
     *
     * @param message     
    * @return     
    */    
    private String getVerificationCode(String message) {
        String code = null;
        int index = message.indexOf(Config.OTP_DELIMITER);

        if (index != -1) {
            int start = index + 2;
            int length = 6;
            code = message.substring(start, start + length);
            return code;
        }

        return code;
    }

}

Important robustness and API notes (you should apply these to the above code):

  • Use Telephony.Sms.Intents.getMessagesFromIntent(intent) to get SmsMessage[] which handles multipart PDUs and format differences.
  • For API >= 23 use SmsMessage.createFromPdu(byte[] pdu, String format) if you must call createFromPdu.
  • Validate bounds before substringing the OTP: ensure start + length <= message.length().
  • Avoid logging or Toasting the OTP (sensitive information).

3. AndroidManifest.xml changes

Add required permissions and register the receiver and service. The original manifest snippets:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />

And inside <application> (excerpt):

<application android:name=".app.MyApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme">

    <activity android:name=".activity.SmsActivity" android:label="@string/title_activity_sms">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <activity android:name=".activity.MainActivity" android:label="@string/app_name" android:windowSoftInputMode="adjustResize" />

    <!-- SMS Receiver -->
    <receiver android:name=".receiver.SmsReceiver">
      <intent-filter android:priority="99999">
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
      </intent-filter>
    </receiver>

    <!-- Intent service -->
    <service android:name=".service.HttpService" android:exported="false" />

</application>

Notes & cautions:

  • Runtime permissions: RECEIVE_SMS and READ_SMS require requesting the permission at runtime on Android 6+. Add code in your activity to request and handle user response.
  • Very high receiver priority is discouraged; it can block other SMS handlers. Consider lowering priority or using SMS Retriever / User Consent to avoid requiring READ_SMS.
  • On Android 8+ you cannot reliably start background services from a receiver; consider JobIntentService or WorkManager.

4. activity_sms.xml (res/layout)

Create the registration / verification screens. The original layout is long; include it as given:

<!-- activity_sms.xml content -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/viewContainer" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="marutsoft.com.smsverify.activity.SmsActivity">

    <marutsoft.com.smsverify.helper.MyViewPager        android:id="@+id/viewPagerVertical"        android:layout_width="fill_parent"        android:layout_height="fill_parent">

        <LinearLayout            android:id="@+id/layout_sms"            android:layout_width="fill_parent"            android:layout_height="wrap_content"            android:background="@color/colorPrimary"            android:gravity="center_horizontal"            android:orientation="vertical">

            <ImageView                android:layout_width="60dp"                android:layout_height="60dp"                android:layout_gravity="center_horizontal"                android:layout_marginBottom="25dp"                android:layout_marginTop="100dp"                android:src="@mipmap/ic_launcher" />

            <TextView                android:layout_width="fill_parent"                android:layout_height="wrap_content"                android:layout_marginBottom="25dp"                android:gravity="center_horizontal"                android:inputType="textCapWords"                android:paddingLeft="40dp"                android:paddingRight="40dp"                android:text="@string/msg_enter_mobile"                android:textColor="@android:color/white"                android:textSize="14dp" />

            <LinearLayout                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:orientation="vertical">

                <EditText                    android:id="@+id/inputName"                    android:layout_width="fill_parent"                    android:layout_height="wrap_content"                    android:layout_marginBottom="15dp"                    android:background="@android:color/white"                    android:fontFamily="sans-serif-light"                    android:hint="@string/lbl_name"                    android:padding="5dp"                    android:singleLine="true"                    android:textColor="@color/colorPrimary"                    android:textSize="18dp" />

                <EditText                    android:id="@+id/inputEmail"                    android:layout_width="fill_parent"                    android:layout_height="wrap_content"                    android:layout_marginBottom="15dp"                    android:background="@android:color/white"                    android:fontFamily="sans-serif-light"                    android:hint="@string/lbl_email"                    android:inputType="textEmailAddress"                    android:padding="5dp"                    android:textColor="@color/colorPrimary"                    android:textSize="18dp" />

                <EditText                    android:id="@+id/inputMobile"                    android:layout_width="240dp"                    android:layout_height="wrap_content"                    android:background="@android:color/white"                    android:fontFamily="sans-serif-light"                    android:hint="@string/lbl_mobile"                    android:inputType="phone"                    android:maxLength="10"                    android:padding="5dp"                    android:textColor="@color/colorPrimary"                    android:textCursorDrawable="@null"                    android:textSize="18dp" />

                <Button                    android:id="@+id/btn_request_sms"                    android:layout_width="fill_parent"                    android:layout_height="wrap_content"                    android:layout_gravity="center_horizontal"                    android:layout_marginTop="25dp"                    android:background="@color/colorPrimaryDark"                    android:text="@string/lbl_next"                    android:textColor="@android:color/white"                    android:textSize="14dp" />
            </LinearLayout>


        </LinearLayout>

        <LinearLayout            android:id="@+id/layout_otp"            android:layout_width="fill_parent"            android:layout_height="fill_parent"            android:background="@color/colorPrimary"            android:gravity="center_horizontal"            android:orientation="vertical">

            <ImageView                android:layout_width="60dp"                android:layout_height="60dp"                android:layout_gravity="center_horizontal"                android:layout_marginBottom="25dp"                android:layout_marginTop="100dp"                android:src="@mipmap/ic_launcher" />

            <TextView                android:layout_width="fill_parent"                android:layout_height="wrap_content"                android:layout_marginBottom="15dp"                android:gravity="center_horizontal"                android:paddingLeft="40dp"                android:paddingRight="40dp"                android:text="@string/msg_sit_back"                android:textColor="@android:color/white"                android:textSize="16dp" />

            <TextView                android:layout_width="fill_parent"                android:layout_height="wrap_content"                android:layout_marginBottom="25dp"                android:gravity="center_horizontal"                android:paddingLeft="40dp"                android:paddingRight="40dp"                android:text="@string/msg_manual_otp"                android:textColor="@android:color/white"                android:textSize="12dp" />

            <EditText                android:id="@+id/inputOtp"                android:layout_width="120dp"                android:layout_height="wrap_content"                android:background="@android:color/white"                android:fontFamily="sans-serif-light"                android:gravity="center_horizontal"                android:hint="@string/lbl_enter_otp"                android:inputType="number"                android:maxLength="6"                android:padding="10dp"                android:textCursorDrawable="@null"                android:textSize="18dp" />

            <Button                android:id="@+id/btn_verify_otp"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_gravity="center_horizontal"                android:layout_marginTop="25dp"                android:background="@color/colorPrimaryDark"                android:paddingLeft="20dp"                android:paddingRight="20dp"                android:text="@string/lbl_submit"                android:textColor="@android:color/white"                android:textSize="14dp" />

        </LinearLayout>

    </marutsoft.com.smsverify.helper.MyViewPager>

    <ProgressBar        android:id="@+id/progressBar"        android:layout_width="30dp"        android:layout_height="30dp"        android:layout_alignParentBottom="true"        android:layout_centerHorizontal="true"        android:layout_gravity="center"        android:layout_marginBottom="60dp"        android:indeterminateTint="@color/colorAccent"        android:indeterminateTintMode="src_atop"        android:visibility="gone" />

    <LinearLayout        android:id="@+id/layout_edit_mobile"        android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:layout_alignParentBottom="true"        android:layout_marginBottom="50dp"        android:gravity="center"        android:orientation="horizontal">

        <TextView            android:id="@+id/txt_edit_mobile"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:textColor="@android:color/white"            android:textSize="16dp" />

        <ImageButton            android:id="@+id/btn_edit_mobile"            android:layout_width="40dp"            android:layout_height="40dp"            android:layout_marginLeft="10dp"            android:background="@null"            android:src="@drawable/ic_edit_mobile" />
    </LinearLayout>

</RelativeLayout>

Add content descriptions (android:contentDescription) for images and ensure labels are present for accessibility.


5. SmsActivity.java (activity directory)

This activity shows the registration screen, requests an OTP from the server, and allows manual OTP entry. It also starts HttpService with the entered OTP.

Code (copy into activity/SmsActivity.java):

package marutsoft.com.smsverify.activity;

import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.Map;

import marutsoft.com.smsverify.R;
import marutsoft.com.smsverify.app.Config;
import marutsoft.com.smsverify.app.MyApplication;
import marutsoft.com.smsverify.helper.PrefManager;
import marutsoft.com.smsverify.service.HttpService;

/**
 * Created by Karjol G T on 02-02-2016.
 */public class SmsActivity extends AppCompatActivity implements View.OnClickListener {

    private static String TAG = SmsActivity.class.getSimpleName();

    private ViewPager viewPager;
    private ViewPagerAdapter adapter;
    private Button btnRequestSms, btnVerifyOtp;
    private EditText inputName, inputEmail, inputMobile, inputOtp;
    private ProgressBar progressBar;
    private PrefManager pref;
    private ImageButton btnEditMobile;
    private TextView txtEditMobile;
    private LinearLayout layoutEditMobile;


    @Override    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sms);

        viewPager = (ViewPager) findViewById(R.id.viewPagerVertical);
        inputName = (EditText) findViewById(R.id.inputName);
        inputEmail = (EditText) findViewById(R.id.inputEmail);
        inputMobile = (EditText) findViewById(R.id.inputMobile);
        inputOtp = (EditText) findViewById(R.id.inputOtp);
        btnRequestSms = (Button) findViewById(R.id.btn_request_sms);
        btnVerifyOtp = (Button) findViewById(R.id.btn_verify_otp);
        progressBar = (ProgressBar) findViewById(R.id.progressBar);
        btnEditMobile = (ImageButton) findViewById(R.id.btn_edit_mobile);
        txtEditMobile = (TextView) findViewById(R.id.txt_edit_mobile);
        layoutEditMobile = (LinearLayout) findViewById(R.id.layout_edit_mobile);

        // view click listeners        
        btnEditMobile.setOnClickListener(this);
        btnRequestSms.setOnClickListener(this);
        btnVerifyOtp.setOnClickListener(this);

        // hiding the edit mobile number        
        layoutEditMobile.setVisibility(View.GONE);

        pref = new PrefManager(this);

        // Checking for user session        // if user is already logged in, take him to main activity        
        if (pref.isLoggedIn()) {
            Intent intent = new Intent(SmsActivity.this, MainActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
            startActivity(intent);

            finish();
        }

        adapter = new ViewPagerAdapter();
        viewPager.setAdapter(adapter);
        //viewPager.setOnP        
        viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override            
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            }

            @Override            
            public void onPageSelected(int position) {
            }

            @Override            
            public void onPageScrollStateChanged(int state) {

            }
        });


        /**         * Checking if the device is waiting for sms         * showing the user OTP screen         */        
        if (pref.isWaitingForSms()) {
            viewPager.setCurrentItem(1);
            layoutEditMobile.setVisibility(View.VISIBLE);
        }
    }

    @Override    
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_request_sms:
                validateForm();
                break;

            case R.id.btn_verify_otp:
                verifyOtp();
                break;

            case R.id.btn_edit_mobile:
                viewPager.setCurrentItem(0);
                layoutEditMobile.setVisibility(View.GONE);
                pref.setIsWaitingForSms(false);
                break;
        }
    }

    /**     * Validating user details form     */    
    private void validateForm() {
        String name = inputName.getText().toString().trim();
        String email = inputEmail.getText().toString().trim();
        String mobile = inputMobile.getText().toString().trim();

        // validating empty name and email        
        if (name.length() == 0 || email.length() == 0) {
            Toast.makeText(getApplicationContext(), "Please enter your details", Toast.LENGTH_SHORT).show();
            return;
        }

        // validating mobile number        // it should be of 10 digits length        
        if (isValidPhoneNumber(mobile)) {

            // request for sms            
            progressBar.setVisibility(View.VISIBLE);

            // saving the mobile number in shared preferences            
            pref.setMobileNumber(mobile);

            // requesting for sms            
            requestForSMS(name, email, mobile);

        } else {
            Toast.makeText(getApplicationContext(), "Please enter valid mobile number", Toast.LENGTH_SHORT).show();
        }
    }

    /**     * Method initiates the SMS request on the server     *
     * @param name   user name     * @param email  user email address     * @param mobile user valid mobile number     */    
    private void requestForSMS(final String name, final String email, final String mobile) {
        StringRequest strReq = new StringRequest(Request.Method.POST,
                Config.URL_REQUEST_SMS, new Response.Listener<String>() {

            @Override            
            public void onResponse(String response) {
                Log.d(TAG, response.toString());

                try {
                    JSONObject responseObj = new JSONObject(response);

                    // Parsing json object response                    // response will be a json object                    
                    boolean error = responseObj.getBoolean("error");
                    String message = responseObj.getString("message");

                    // checking for error, if not error SMS is initiated                    // device should receive it shortly                    
                    if (!error) {
                        // boolean flag saying device is waiting for sms                        
                        pref.setIsWaitingForSms(true);

                        // moving the screen to next pager item i.e otp screen                        
                        viewPager.setCurrentItem(1);
                        txtEditMobile.setText(pref.getMobileNumber());
                        layoutEditMobile.setVisibility(View.VISIBLE);

                        Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();

                    } else {
                        Toast.makeText(getApplicationContext(),
                                "Error: " + message,
                                Toast.LENGTH_LONG).show();
                    }

                    // hiding the progress bar                    
                    progressBar.setVisibility(View.GONE);

                } catch (JSONException e) {
                    Toast.makeText(getApplicationContext(),
                            "Error: " + e.getMessage(),
                            Toast.LENGTH_LONG).show();

                    progressBar.setVisibility(View.GONE);
                }

            }
        }, new Response.ErrorListener() {

            @Override            
            public void onErrorResponse(VolleyError error) {
                Log.e(TAG, "Error: " + error.getMessage());
                Toast.makeText(getApplicationContext(),
                        error.getMessage(), Toast.LENGTH_SHORT).show();
                progressBar.setVisibility(View.GONE);
            }
        }) {

            /**             * Passing user parameters to our server             *             */            
            @Override            
            protected Map<String, String> getParams() {
                Map<String, String> params = new HashMap<String, String>();
                params.put("name", name);
                params.put("email", email);
                params.put("mobile", mobile);

                Log.e(TAG, "Posting params: " + params.toString());

                return params;
            }

        };

        // Adding request to request queue        
        MyApplication.getInstance().addToRequestQueue(strReq);
    }

    /**     * sending the OTP to server and activating the user     */    
    private void verifyOtp() {
        String otp = inputOtp.getText().toString().trim();

        if (!otp.isEmpty()) {
            Intent grapprIntent = new Intent(getApplicationContext(), HttpService.class);
            grapprIntent.putExtra("otp", otp);
            startService(grapprIntent);
        } else {
            Toast.makeText(getApplicationContext(), "Please enter the OTP", Toast.LENGTH_SHORT).show();
        }
    }

    /**     * Regex to validate the mobile number     * mobile number should be of 10 digits length     *
     * @param mobile     * @return     */    
    private static boolean isValidPhoneNumber(String mobile) {
        String regEx = "^[0-9]{10}$";
        return mobile.matches(regEx);
    }


    class ViewPagerAdapter extends PagerAdapter {

        @Override        
        public int getCount() {
            return 2;
        }

        @Override        
        public boolean isViewFromObject(View view, Object object) {
            return view == ((View) object);
        }

        public Object instantiateItem(View collection, int position) {

            int resId = 0;
            switch (position) {
                case 0:
                    resId = R.id.layout_sms;
                    break;
                case 1:
                    resId = R.id.layout_otp;
                    break;
            }
            return findViewById(resId);
        }
    }


}

Notes: Add runtime permission request flow before attempting to read SMS or register the receiver.


6. Toolbar layout (res/layout/toolbar.xml)

Toolbar XML used by MainActivity:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android" xmlns:local="http://schemas.android.com/apk/res-auto" android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="?attr/actionBarSize" android:background="?attr/colorPrimary" local:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" local:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

7. activity_main.xml (layout for the logged in screen)

Original code for the main activity layout:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">

    <LinearLayout        android:id="@+id/layout_toolbar"        android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:layout_alignParentTop="true"        android:orientation="vertical">

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

    <LinearLayout        android:id="@+id/layout_mobile"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerInParent="true"        android:orientation="vertical">

        <TextView            android:id="@+id/name"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:padding="10dp"            android:textSize="18dp" />

        <TextView            android:id="@+id/email"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:padding="10dp"            android:textSize="18dp" />

        <TextView            android:id="@+id/mobile"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:padding="10dp"            android:textSize="18dp" />

    </LinearLayout>
</RelativeLayout>

8. menu_main.xml (menu)

Menu contains a logout action:

<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
  <item        android:id="@+id/action_logout"        android:orderInCategory="100"        android:title="@string/action_logout"        app:showAsAction="always" />
</menu>

9. MainActivity.java (showing stored profile and logout)

Code (copy into activity/MainActivity.java):

package marutsoft.com.smsverify.activity;

import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;

import java.util.HashMap;

import marutsoft.com.smsverify.R;
import marutsoft.com.smsverify.helper.PrefManager;

public class MainActivity extends AppCompatActivity {

    private Toolbar toolbar;
    private PrefManager pref;
    private TextView name, email, mobile;

    @Override    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        toolbar = (Toolbar) findViewById(R.id.toolbar);
        name = (TextView) findViewById(R.id.name);
        email = (TextView) findViewById(R.id.email);
        mobile = (TextView) findViewById(R.id.mobile);

        // enabling toolbar        
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayShowHomeEnabled(true);


        pref = new PrefManager(getApplicationContext());


        // Checking if user session        // if not logged in, take user to sms screen        
        if (!pref.isLoggedIn()) {
            logout();
        }

        // Displaying user information from shared preferences        
        HashMap<String, String> profile = pref.getUserDetails();
        name.setText("Name: " + profile.get("name"));
        email.setText("Email: " + profile.get("email"));
        mobile.setText("Mobile: " + profile.get("mobile"));
    }

    /**     * Logging out user     * will clear all user shared preferences and navigate to     * sms activation screen     */    
    private void logout() {
        pref.clearSession();

        Intent intent = new Intent(MainActivity.this, SmsActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);

        startActivity(intent);

        finish();
    }


    @Override    
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override    
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement       
        if (id == R.id.action_logout) {
            logout();
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

10. Run and test

With proper configuration in Config.java (URLs for URL_REQUEST_SMS and URL_VERIFY_OTP) and a server endpoint that sends OTP via SMS to the supplied mobile number, you can run the application and test sign‑up and OTP verification.

screenshot

That's it. Happy Coding


Android versions & security (must read before copying the code)

  • Android 6 (Marshmallow) and above: READSMS and RECEIVESMS are runtime permissions. You must request them in the Activity and handle the user's decision. If the user denies, fall back to manual OTP entry.
  • Android 8 (Oreo) and above: apps cannot freely start background services from a BroadcastReceiver. Replace IntentService with JobIntentService or schedule work with WorkManager for network calls triggered from a receiver.
  • SMS Retriever API / SMS User Consent API: to avoid asking for SMS reading permissions and to comply with Play policy, prefer the SMS Retriever API or the User Consent API. These allow auto‑retrieval of OTP without READ_SMS.
  • Privacy & security: never log or display the OTP in Toasts or logs in production. Always use HTTPS for server requests and verify OTP server‑side. Add rate limiting and expiry on server side.

If you want, I can produce a small patch for the SmsReceiver (using Telephony.Sms.Intents.getMessagesFromIntent) and a JobIntentService replacement for HttpService as an example.


References and further reading (recommended):

No comments: