Aplikasi SMS Gateway dengan Android dan NG Framework (Bagian II)

Bagikan jika Anda sukai postingan ini!

Notifikasi SMS Terkirim
Notifikasi SMS Terkirim

Setelah sebelumnya kita menyiapkan web service untuk keperluan SMS Gateway menggunakan NG Framework, kali ini kita tinggal membuat aplikasi Android untuk keperluan pengiriman SMS. Basis aplikasi yang kita buat hampir mirip dengan contoh aplikasi pada artikel Menampilkan Notifikasi Setiap Jam dengan Service. Sebaiknya Anda sudah mempelajari artikel tersebut sehingga Anda tidak kebingungan mengikuti langkah-langkah pengembangan aplikasi pada artikel ini.

Kita mulai dengan membuat proyek baru dengan pola Basic Activity dengan pengaturan seperti di bawah ini.

  • Name: SMS Gateway
  • Package: com.inochi.smsgateway
  • Language: Java

Sama seperti pada artikel yang lalu, tambahkan beberapa paket baru pada com.inochi.smsgateway, yaitu: helper, item, service, util, task, dan listener. Kemudian tambahkan beberapa kelas berikut ini pada paket helper.

Kelas Constants

package com.inochi.smsgateway.helper;

public final class Constants {
    public static class Default {
        public static final String APP = "com.inochi.smsgateway";
        private static final int REQUEST = 2000;
    }

    public static final class Action {
        public static final String CLOSE_NOTIFY = Default.APP + ".action." + "CLOSE_NOTIFY";
        public static final String SHOW_NOTIFY = Default.APP + ".action." + "SHOW_NOTIFY";
        public static final String CREATE_DAILY = Default.APP + ".action." + "CREATE_DAILY";
        public static final String REMOVE_DAILY = Default.APP + ".action." + "REMOVE_DAILY";
        public static final String CHECK_OUTBOX = Default.APP + ".action." + "CHECK_OUTBOX";
    }

    public static final class Setting {
        public static final String BOOT_PERMISSION = Default.APP + ".setting." + "BOOT_PERMISSION";
        public static final String IP_SERVER = Default.APP + ".setting." + "IP_SERVER";
        public static final String CHECK_DURATION = Default.APP + ".setting." + "CHECK_DURATION";
        public static final String NOTIF_ID = Default.APP + ".setting." + "NOTIF_ID";
        public static final String NOTIF_TITLE = Default.APP + ".setting." + "NOTIF_TITLE";
        public static final String NOTIF_TEXT = Default.APP + ".setting." + "NOTIF_TEXT";
        public static final String NOTIF_ICON = Default.APP + ".setting." + "NOTIF_ICON";
    }

    public static final class Permission {
        public static final class Type {
            public static final int RECEIVE_BOOT_COMPLETED = Default.REQUEST + 1;
            public static final int WAKE_LOCK = Default.REQUEST + 2;
            public static final int VIBRATE = Default.REQUEST + 3;
            public static final int READ_SMS = Default.REQUEST + 4;
            public static final int RECEIVE_SMS = Default.REQUEST + 5;
            public static final int SEND_SMS = Default.REQUEST + 6;
        }
    }
}

Kelas Settings

package com.inochi.smsgateway.helper;

import android.content.Context;
import android.content.SharedPreferences;

public class Settings {
    private SharedPreferences settings;
    private Context context;
    private String name;

    public Settings(Context context, String name){
        this.context = context;
        this.name = name;
        settings = context.getSharedPreferences(name, Context.MODE_PRIVATE);
    }

    public Settings(Context context){
        this(context, context.getPackageName());
    }

    public void setSetting(String key, String value){
        SharedPreferences.Editor editor = settings.edit();
        editor.putString(key, value);
        editor.apply();
    }

    public void deleteSetting(){
        settings = context.getSharedPreferences(name, Context.MODE_PRIVATE);
        settings.edit().clear().apply();
    }

    public String getSetting(String key, String defVal){
        return settings.getString(key, defVal);
    }

    public String getSetting(String key){
        return settings.getString(key, "");
    }

    public int getIntSetting(String strKey, int defVal){
        String strValue = String.valueOf(defVal);
        String strSetting = getSetting(strKey, strValue);

        return Integer.parseInt(strSetting);
    }

    public int getIntSetting(String strKey){
        String strSetting = getSetting(strKey);
        return Integer.parseInt(strSetting);
    }

    public void setIntSetting(String strKey, int defVal){
        String strValue = String.valueOf(defVal);
        setSetting(strKey, strValue);
    }

    public short getShortSetting(String strKey, short defVal){
        String strValue = String.valueOf(defVal);
        String strSetting = getSetting(strKey, strValue);
        return Short.parseShort(strSetting);
    }

    public void setShortSetting(String strKey, short defVal){
        String strValue = String.valueOf(defVal);
        setSetting(strKey, strValue);
    }

    public long getLongSetting(String strKey, long defVal){
        String strValue = String.valueOf(defVal);
        String strSetting = getSetting(strKey, strValue);

        return Long.parseLong(strSetting);
    }

    public long getLongSetting(String strKey){
        String strSetting = getSetting(strKey);
        return Long.parseLong(strSetting);
    }

    public void setLongSetting(String strKey, long defVal){
        String strValue = String.valueOf(defVal);
        setSetting(strKey, strValue);
    }
}

Kelas BundleSettings

package com.inochi.smsgateway.helper;

import android.content.Context;

public class BundleSettings {
    private Context context;
    private Settings settings;

    public BundleSettings(Context context){
        this.context = context;
        this.settings = new Settings(context);
    }

    public int getBootPremission(){
        return settings.getIntSetting(Constants.Setting.BOOT_PERMISSION, 0);
    }

    public void setBootPremission(int value){
        settings.setIntSetting(Constants.Setting.BOOT_PERMISSION, value);
    }

    public String getIPServer(){
        return settings.getSetting(Constants.Setting.IP_SERVER, "");
    }

    public void setIPServer(String value){
        settings.setSetting(Constants.Setting.IP_SERVER, value);
    }

    public int getCheckDuration(){
        return settings.getIntSetting(Constants.Setting.CHECK_DURATION, 1);
    }

    public void setCheckDuration(int value){
        settings.setIntSetting(Constants.Setting.CHECK_DURATION, value);
    }
}

Tambahkan sebuah kelas dengan nama NotifItem pada paket item. Lengkapi kode sumbernya seperti di bawah ini.

package com.inochi.smsgateway.item;

import java.io.Serializable;

public class NotifItem implements Serializable {
    private int id;
    private String ticker;
    private String title;
    private String message;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getTicker() {
        return ticker;
    }

    public void setTicker(String ticker) {
        this.ticker = ticker;
    }
}

Masih pada paket item, tambahkan sebuah kelas baru lagi dengan nama SMSItem dan lengkapi kode sumbernya seperti di bawah ini.

package com.adidaya.item;

import java.io.Serializable;

public class SMSItem implements Serializable {
    private int id;
    private String receiver;
    private String message;
    private int status;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getReceiver() {
        return receiver;
    }

    public void setReceiver(String receiver) {
        this.receiver = receiver;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }
}

Pada paket util tambahkan UI Component -> Notification (lihat caranya pada artikel Menampilkan Notifikasi Setiap Jam dengan Service), beri nama dengan SMSNotification. Ubah kode sumbernya menjadi seperti di bawah ini.

package com.inochi.smsgateway.util;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.support.v4.app.NotificationCompat;

import com.inochi.smsgateway.R;
import com.inochi.smsgateway.helper.Constants;
import com.inochi.smsgateway.item.NotifItem;
import com.inochi.smsgateway.service.SMSReceiver;

public class SMSNotification {
    public static void notify(final Context context, NotifItem notifItem) {
        final int id = notifItem.getId();
        final String ticker = notifItem.getTicker();
        final String title = notifItem.getTitle();
        final String text = notifItem.getMessage();

        Intent intentReceiver = new Intent(context, SMSReceiver.class);
        intentReceiver.putExtra(Constants.Setting.NOTIF_ID, id);
        intentReceiver.setAction(Constants.Action.CLOSE_NOTIFY);

        PendingIntent closeNotifyIntent = PendingIntent.getBroadcast(context,
                id, intentReceiver, PendingIntent.FLAG_UPDATE_CURRENT);

        long[] pattern = null;
        NotificationManager notificationManager = createNotificationChannel(context);

        final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, Constants.Default.APP)
                .setDefaults(NotificationCompat.FLAG_AUTO_CANCEL)
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle(title)
                .setContentText(text)
                .setTicker(ticker)
                .setVibrate(pattern)
                .setStyle(new NotificationCompat.BigTextStyle().bigText(text))
                .setAutoCancel(true)
                .addAction(android.R.drawable.ic_menu_close_clear_cancel, "Close", closeNotifyIntent)
                ;

        notificationManager.notify(id, builder.build());
    }

    private static NotificationManager createNotificationChannel(Context context) {
        NotificationManager notificationManager;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            notificationManager = context.getSystemService(NotificationManager.class);
        } else {
            notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            CharSequence name = context.getString(R.string.app_name);
            String description = context.getString(R.string.app_name);
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(Constants.Default.APP, name, importance);
            channel.setDescription(description);
            notificationManager.createNotificationChannel(channel);
        }

        return notificationManager;
    }

    public static void cancel(final Context context, int id) {
        final NotificationManager nm = createNotificationChannel(context);
        nm.cancel(id);
    }
}

Abaikan dulu jika terdapat kesalahan (error). Tambahkan sebuah kelas Broadcast Receiver pada paket service, beri nama dengan SMSReceiver kemudian ubah kode sumber menjadi seperti di bawah ini.

package com.inochi.smsgateway.service;

import android.content.Context;
import android.content.Intent;
import android.support.v4.content.WakefulBroadcastReceiver;

public class SMSReceiver extends WakefulBroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action == null) action = "";

        if (!action.isEmpty()){
            Intent service = new Intent(context, SMSService.class);
            service.setAction(action);
            service.putExtras(intent);
            startWakefulService(context, service);
        }
    }
}

Tambahkan sebuah kelas Service pada paket service, beri nama dengan SMSService kemudian ubah kode sumber menjadi seperti di bawah ini.

package com.inochi.smsgateway.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;

public class SMSService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

    public SMSService() {
        super();
        mName = "MainService";
    }

    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    public void onHandleIntent(Intent intent){
        SMSReceiver.completeWakefulIntent(intent);
    }
}

Kode sumber pada kelas SMSService belum selesai, kita tinggalkan dulu. Kita lanjutkan ke proses berikutnya. Untuk dapat mengakses web service, saya biasanya menggunakan pustaka org.jsoup. Untuk menambahkan pustaka, pada panel Project, klik ganda cabang Gradle Scripts, kemudian klik ganda cabang build.gradle (Module: app).

Tambahkan perintah berikut ini pada bracket dependencies.

implementation 'org.jsoup:jsoup:1.11.3'

Kemudian klik tautan Sync Now di kiri atas jendela kode. Pastikan komputer Anda terhubung ke internet dan tunggu beberapa saat sampai proses sinkronisasi selesai.

Menambahkan pustaka org.jsoup
Menambahkan pustaka org.jsoup

Tambahkan kelas baru dengan nama HttpHelper pada paket helper, dan lengkapi kode sumbernya menjadi seperti di bawah ini.

package com.inochi.smsgateway.helper;

import android.os.StrictMode;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

import java.io.IOException;

public class HttpHelper {
	public HttpHelper(){
		StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
		StrictMode.setThreadPolicy(policy);
	}

	public String getJson(String url) {
		String strBody = "";
		try {
			Document document = Jsoup.connect(url).ignoreContentType(true).get();
			strBody = document.body().html();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return strBody;
	}
}

Tambahkan kelas baru pada paket listener, beri nama GetListener, lengkapi kode sumbernya menjadi seperti di bawah ini.

package com.inochi.smsgateway.listener;

public interface GetListener {
    void onGetResult(String result);
}

Berikutnya tambahkan kelas baru pada paket task, beri nama AsyncGet, lengkapi kode sumbernya menjadi seperti di bawah ini.

package com.inochi.smsgateway.task;

import android.os.AsyncTask;

import com.inochi.smsgateway.helper.HttpHelper;
import com.inochi.smsgateway.listener.GetListener;

public class AsyncGet extends AsyncTask<String, Integer, String> {
    private GetListener delegate;

    public AsyncGet() {

    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected String doInBackground(String... params) {
        String result = "";
        try {
            String url = params[0];
            HttpHelper httpHelper = new HttpHelper();
            result = httpHelper.getJson(url);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return result;
    }

    @Override
    protected void onPostExecute(String result) {
        if (result != null){
            if (delegate != null)
                delegate.onGetResult(result);
        }
    }

    public void setDelegate(GetListener delegate) {
        this.delegate = delegate;
    }
}

Wah, sudah banyak sekali ya kelas yang kita tambahkan? Tapi ini masih belum selesai lho. Lanjut, tambahkan sebuah kelas baru pada paket helper, beri nama dengan Helper. Lengkapi kode sumbernya menjadi seperti di bawah ini.

package com.inochi.smsgateway.helper;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsManager;

import com.inochi.smsgateway.item.SMSItem;
import com.inochi.smsgateway.listener.GetListener;
import com.inochi.smsgateway.service.SMSReceiver;
import com.inochi.smsgateway.task.AsyncGet;

public class Helper {
    private Context context;
    private BundleSettings bundleSettings;
    private GetListener delegate;

    public Helper(Context context){
        this.context = context;
        this.bundleSettings = new BundleSettings(context);
        this.delegate = (GetListener) context;
    }
    
    public void ping(){
        try {
            String ipServer = bundleSettings.getIPServer();
            String urlConnect = "http://" + ipServer + "/sms-gateway/api/ping";

            AsyncGet asyncGet = new AsyncGet();
            asyncGet.setDelegate(delegate);
            asyncGet.execute(urlConnect);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void readOutbox(){
        try {
            String ipServer = bundleSettings.getIPServer();
            String urlConnect = "http://" + ipServer + "/sms-gateway/api/sms/read";

            AsyncGet asyncGet = new AsyncGet();
            asyncGet.setDelegate(delegate);
            asyncGet.execute(urlConnect);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void updateOutbox(int id){
        try {
            String ipServer = bundleSettings.getIPServer();
            String urlConnect = "http://" + ipServer + "/sms-gateway/api/sms/update/" + id;

            AsyncGet asyncGet = new AsyncGet();
            asyncGet.setDelegate(delegate);
            asyncGet.execute(urlConnect);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void sendSMS(SMSItem smsItem) {
        try {
            String phone = smsItem.getReceiver();
            int id = smsItem.getId();
            if (phone != null){
                if (!phone.isEmpty()){
                    String message = smsItem.getMessage();
                    if (message != null){
                        if (!message.isEmpty()){
                            SmsManager smsManager = SmsManager.getDefault();
                            smsManager.sendTextMessage(phone, null, message, null, null);

                            Intent intent = new Intent(context, SMSReceiver.class);
                            intent.setAction(Constants.Action.SHOW_NOTIFY);

                            Bundle args = new Bundle();
                            args.putInt(Constants.Setting.NOTIF_ID, 1000 + id);
                            args.putString(Constants.Setting.NOTIF_TITLE, "SMS Send to " + phone);
                            args.putString(Constants.Setting.NOTIF_TEXT, "Message: " + message);
                            intent.putExtras(args);
                            context.sendBroadcast(intent);

                            updateOutbox(id);
                        }
                    }
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void setDelegate(GetListener delegate) {
        this.delegate = delegate;
    }
}

Sekarang kita akan mengubah layout dari content_main.xml menjadi seperti di bawah ini.

<?xml version="1.0" encoding="utf-8"?>

<android.support.constraint.ConstraintLayout
    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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context=".MainActivity"
    tools:showIn="@layout/activity_main">

    <FrameLayout
        android:id="@+id/fraMain"
        android:padding="8dp"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

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

            <TableRow
                android:id="@+id/tblIPServer"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" >

                <TextView
                    android:id="@+id/tvwIPServer"
                    android:layout_width="100dp"
                    android:layout_height="wrap_content"
                    android:text="IP Server" />

                <EditText
                    android:id="@+id/editIPServer"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:ems="10"
                    android:inputType="textPersonName" />

            </TableRow>

            <TableRow
                android:id="@+id/tblDuration"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" >

                <TextView
                    android:id="@+id/tvwDuration"
                    android:layout_width="100dp"
                    android:layout_height="wrap_content"
                    android:text="Duration" />

                <EditText
                    android:id="@+id/editDuration"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:ems="10"
                    android:inputType="number" />

            </TableRow>

            <Button
                android:id="@+id/btnConnect"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:text="Hubungkan" />

            <Button
                android:id="@+id/btnSetting"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:visibility="gone"
                android:text="Pengaturan" />

        </LinearLayout>
    </FrameLayout>
</android.support.constraint.ConstraintLayout>

Berikut ini tampilan dari layout content_main.xml.

Layout content_main.xml
Layout content_main.xml

Buka kembali berkas kelas SMSService pada paket service, lengkapi kode sumbernya menjadi seperti di bawah ini.

package com.inochi.smsgateway.service;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;

import com.inochi.smsgateway.helper.BundleSettings;
import com.inochi.smsgateway.helper.Constants;
import com.inochi.smsgateway.helper.Helper;
import com.inochi.smsgateway.item.NotifItem;
import com.inochi.smsgateway.item.SMSItem;
import com.inochi.smsgateway.listener.GetListener;
import com.inochi.smsgateway.util.SMSNotification;

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

import java.util.ArrayList;
import java.util.Calendar;

public class SMSService extends Service implements GetListener {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;

    private final class ServiceHandler extends Handler {
        ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

    public SMSService() {
        super();
        mName = "MainService";
    }

    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    public void onHandleIntent(Intent intent){
        String action = intent.getAction();

        if (action == null) action = "";

        if (!action.isEmpty()){
            Bundle args = intent.getExtras();
            NotifItem notifItem = null;

            int notifId = 0;

            String notifTitle;
            String notifText;

            if (args != null){
                notifId = args.getInt(Constants.Setting.NOTIF_ID);
                notifTitle = args.getString(Constants.Setting.NOTIF_TITLE);
                notifText = args.getString(Constants.Setting.NOTIF_TEXT);
                if (notifId > 0){
                    notifItem = new NotifItem();
                    notifItem.setId(notifId);
                    notifItem.setTicker(notifTitle);
                    notifItem.setTitle(notifTitle);
                    notifItem.setMessage(notifText);
                }
            }

            switch (action){
                case "com.htc.intent.action.QUICKBOOT_POWERON":
                case "android.intent.action.QUICKBOOT_POWERON":
                case "android.intent.action.BOOT_COMPLETED":
                case Constants.Action.CREATE_DAILY:
                    enableService();
                    break;
                case Constants.Action.REMOVE_DAILY:
                    disableService();
                    stopSelf();
                    break;
                case Constants.Action.CHECK_OUTBOX:
                    checkOutbox();
                    break;
                case Constants.Action.SHOW_NOTIFY:
                    if (notifId > 0)
                        SMSNotification.notify(this, notifItem);
                    break;
                case Constants.Action.CLOSE_NOTIFY:
                    if (notifId > 0)
                        SMSNotification.cancel(this, notifId);
                    break;
            }
        }

        SMSReceiver.completeWakefulIntent(intent);
    }

    private void enableService(){
        BundleSettings bundleSettings = new BundleSettings(this);
        String ipServer = bundleSettings.getIPServer();
        int duration = bundleSettings.getCheckDuration();

        if (!ipServer.isEmpty()){
            Calendar calendar = Calendar.getInstance();

            AlarmManager alarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
            Intent intent = new Intent(this, SMSReceiver.class);
            intent.setAction(Constants.Action.CHECK_OUTBOX);

            PendingIntent alarmIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
            alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
                    duration * 60 * 1000, alarmIntent);

            PackageManager pm  = getPackageManager();
            ComponentName componentName = new ComponentName(this, SMSReceiver.class);
            pm.setComponentEnabledSetting(componentName,PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                    PackageManager.DONT_KILL_APP);

            checkOutbox();
        }
    }

    private void disableService(){
        AlarmManager alarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(this, SMSReceiver.class);
        intent.setAction(Constants.Action.CHECK_OUTBOX);

        PendingIntent alarmIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
        alarmMgr.cancel(alarmIntent);
    }

    private void checkOutbox(){
        Helper helper = new Helper(this);
        helper.readOutbox();
    }

    @Override
    public void onGetResult(String result) {
        if (result != null){
            if (!result.isEmpty()){
                try {
                    JSONArray jsonArray = new JSONArray(result);
                    if (jsonArray.length() > 0){
                        ArrayList<SMSItem> smsItems = new ArrayList<>();
                        for (int j = 0; j < jsonArray.length(); j++) {
                            JSONObject jsonItem = jsonArray.getJSONObject(j);
                            SMSItem smsItem = new SMSItem();
                            if (jsonItem.has("id")){
                                smsItem.setId(jsonItem.getInt("id"));
                            }
                            if (jsonItem.has("status")){
                                smsItem.setStatus(jsonItem.getInt("status"));
                            }
                            if (jsonItem.has("message")){
                                smsItem.setMessage(jsonItem.getString("message"));
                            }
                            if (jsonItem.has("receiver")){
                                smsItem.setReceiver(jsonItem.getString("receiver"));
                            }
                            smsItems.add(smsItem);
                        }

                        if (smsItems.size() > 0){
                            Helper helper = new Helper(this);
                            for (SMSItem smsItem : smsItems){
                                helper.sendSMS(smsItem);
                            }
                        }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Buka kode sumber kelas MainActivity, lengkapi menjadi seperti di bawah ini.

package com.inochi.smsgateway;

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TableRow;
import android.widget.Toast;

import com.inochi.smsgateway.helper.BundleSettings;
import com.inochi.smsgateway.helper.Constants;
import com.inochi.smsgateway.helper.Helper;
import com.inochi.smsgateway.listener.GetListener;
import com.inochi.smsgateway.service.SMSReceiver;

public class MainActivity extends AppCompatActivity implements GetListener {
    private BundleSettings bundleSettings;
    private Helper helper;

    private TableRow tblIPServer;
    private TableRow tblDuration;
    private EditText editIPServer;
    private EditText editDuration;

    private Button btnConnect;
    private Button btnSetting;

    private String ipServer;
    private int duration;
    private boolean isConnect;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        bundleSettings = new BundleSettings(this);
        helper = new Helper(this);

        ipServer = bundleSettings.getIPServer();
        duration = bundleSettings.getCheckDuration();

        tblIPServer = this.findViewById(R.id.tblIPServer);
        tblDuration = this.findViewById(R.id.tblDuration);
        editIPServer = this.findViewById(R.id.editIPServer);
        editDuration = this.findViewById(R.id.editDuration);
        btnConnect = this.findViewById(R.id.btnConnect);
        btnSetting = this.findViewById(R.id.btnSetting);

        editIPServer.setText(ipServer);
        editDuration.setText(String.valueOf(duration));

        btnConnect.setOnClickListener(onButtonClick);
        btnSetting.setOnClickListener(onButtonClick);

        setPermission();
    }

    private void connect(){
        isConnect = false;

        Editable editableIPServer = editIPServer.getText();
        Editable editableDuration = editDuration.getText();

        ipServer = editableIPServer.toString();
        String strDuration = editableDuration.toString();

        if (strDuration.isEmpty()){
            duration = 1;
        } else {
            duration = Integer.parseInt(strDuration);
        }

        if (!ipServer.isEmpty() && duration > 0){
            bundleSettings.setIPServer(ipServer);
            bundleSettings.setCheckDuration(duration);

            helper.ping();
        }

        updateUI();
    }

    private View.OnClickListener onButtonClick = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.btnConnect:
                    connect();
                    break;
                case R.id.btnSetting:
                    isConnect = false;
                    removeService();
                    updateUI();
                    break;
            }
        }
    };

    public void updateUI(){
        if (isConnect){
            tblIPServer.setVisibility(View.GONE);
            tblDuration.setVisibility(View.GONE);
            btnConnect.setVisibility(View.GONE);
            btnSetting.setVisibility(View.VISIBLE);

            Toast.makeText(this, "Server connected", Toast.LENGTH_LONG).show();

            createService();
        } else {
            tblIPServer.setVisibility(View.VISIBLE);
            tblDuration.setVisibility(View.VISIBLE);
            btnConnect.setVisibility(View.VISIBLE);
            btnSetting.setVisibility(View.GONE);

            Toast.makeText(this, "Server disconnected", Toast.LENGTH_LONG).show();
        }
    }

    private void createService(){
        Intent intent = new Intent(this, SMSReceiver.class);
        intent.setAction(Constants.Action.CREATE_DAILY);
        sendBroadcast(intent);
    }

    private void removeService(){
        Intent intent = new Intent(this, SMSReceiver.class);
        intent.setAction(Constants.Action.REMOVE_DAILY);
        sendBroadcast(intent);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        try {
            setPermission();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @SuppressLint("InlinedApi")
    private void setPermission(){
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_BOOT_COMPLETED)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECEIVE_BOOT_COMPLETED},
                    Constants.Permission.Type.RECEIVE_BOOT_COMPLETED);
        } else if (ContextCompat.checkSelfPermission(this, Manifest.permission.WAKE_LOCK)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WAKE_LOCK},
                    Constants.Permission.Type.WAKE_LOCK);
        } else if (ContextCompat.checkSelfPermission(this, Manifest.permission.VIBRATE)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.VIBRATE},
                    Constants.Permission.Type.VIBRATE);
        } else if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_SMS},
                    Constants.Permission.Type.READ_SMS);
        } else if (ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.SEND_SMS},
                    Constants.Permission.Type.SEND_SMS);
        } else if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECEIVE_SMS)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECEIVE_SMS},
                    Constants.Permission.Type.RECEIVE_SMS);
        } else {
            if (bundleSettings.getBootPremission() == 0){
                runAutoStartCustom();
            } else {
                connect();
            }
        }
    }

    private static final Intent[] AUTO_START_INTENTS = {
            new Intent().setComponent(new ComponentName("com.samsung.android.lool",
                    "com.samsung.android.sm.ui.battery.BatteryActivity")),
            new Intent("miui.intent.action.OP_AUTO_START").addCategory(Intent.CATEGORY_DEFAULT),
            new Intent().setComponent(new ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity")),
            new Intent().setComponent(new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.AutobootManageActivity")),
            new Intent().setComponent(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity")),
            new Intent().setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity")),
            new Intent().setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.startupapp.StartupAppListActivity")),
            new Intent().setComponent(new ComponentName("com.oppo.safe", "com.oppo.safe.permission.startup.StartupAppListActivity")),
            new Intent().setComponent(new ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.AddWhiteListActivity")),
            new Intent().setComponent(new ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.BgStartUpManager")),
            new Intent().setComponent(new ComponentName("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.BgStartUpManagerActivity")),
            new Intent().setComponent(new ComponentName("com.asus.mobilemanager", "com.asus.mobilemanager.entry.FunctionActivity")).setData(
                    Uri.parse("mobilemanager://function/entry/AutoStart"))
    };

    private void runAutoStartCustom(){
        for (Intent intent : AUTO_START_INTENTS){
            if (getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
                startActivity(intent);

                bundleSettings.setBootPremission(1);
                break;
            }
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

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

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onGetResult(String result) {
        if (result != null){
            if (!result.isEmpty()){
                if (result.equals("1")){
                    isConnect = true;
                }
            }
        }
        updateUI();
    }
}

Berikutnya ubah berkas manifest untuk mendaftarkan beberapa perizinan yang diperlukan serta penambahan filter-filter untuk receiver dan service.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.inochi.smsgateway">

    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />
    <uses-permission android:name="android.permission.RECEIVE_SMS" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        tools:ignore="AllowBackup,GoogleAppIndexingWarning,UnusedAttribute"
        android:networkSecurityConfig="@xml/network_security_config">

        <service
            android:name=".service.SMSService"
            android:enabled="true"
            android:exported="true">

            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
                <action android:name="android.intent.action.QUICKBOOT_POWERON" />
                <action android:name="com.inochi.smsgateway.action.CREATE_DAILY" />
                <action android:name="com.inochi.smsgateway.action.REMOVE_DAILY" />
                <action android:name="com.inochi.smsgateway.action.CLOSE_NOTIFY" />
                <action android:name="com.inochi.smsgateway.action.CHECK_OUTBOX" />
            </intent-filter>
        </service>

        <receiver
            android:name=".service.SMSReceiver"
            android:enabled="true"
            android:exported="true" >

            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="com.htc.intent.action.QUICKBOOT_POWERON" />
                <action android:name="android.intent.action.QUICKBOOT_POWERON" />
                <action android:name="com.inochi.smsgateway.action.CREATE_DAILY" />
                <action android:name="com.inochi.smsgateway.action.REMOVE_DAILY" />
                <action android:name="com.inochi.smsgateway.action.CLOSE_NOTIFY" />
                <action android:name="com.inochi.smsgateway.action.CHECK_OUTBOX" />
            </intent-filter>

            <intent-filter android:priority="1000">
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </receiver>

        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Anda akan menemukan kesalahan pada baris ke-23 dari berkas manifest, tepatnya pada pengaturan di bawah ini.

android:networkSecurityConfig="@xml/network_security_config"

Sedikit saya jelaskan, pada sistem operasi Android terbaru, yaitu sejak Android 7.0 (Nougat), Android tidak lagi mengizinkan untuk mengakses situs web yang masih menggunakan http sebagai protokolnya, melainkan harus https. Karena web service yang kita buat masih menggunakan protokol http, kita perlu mengakalinya yaitu dengan cara membuat berkas konfigurasi agar Android melewati pemeriksaan protokol https. Caranya yaitu, klik kanan pada cabang res di panel Project, buat sebuah Directory baru, beri nama dengan xml. Kemudian pada directory xml tersebut tambahkan sebuah berkas Android Resource File, beri nama dengan network_security_config.xml, ubah skripnya menjadi seperti di bawah ini.

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

Sekarang Anda sudah bisa mengujicoba aplikasi. Pastikan komputer dan perangkat Android (ponsel) Anda terhubung pada satu jaringan wi-fi yang sama. Ketahui alamat IP dari komputer Anda yang merupakan server lokal dari web service. Jalankan proyek aplikasi Anda, isikan alamat IP komputer (web service) Anda pada kotak IP Server. Kemudian klik tombol HUBUNGKAN.

Menentukan IP Server pada aplikasi Android
Menentukan IP Server pada aplikasi Android

Jika aplikasi terhubung dengan web service, maka akan ditampilkan toast dengan pesan “Server connected“. Setelah terhubung, aplikasi akan memanggil web service untuk mengambil data dari tabel outbox. Jika ditemukan data (dengan nilai status = 0) pada tabel outbox, aplikasi akan mengirimkan SMS kepada penerima, kemudian aplikasi akan menampilkan notifikasi pengiriman SMS.

Notifikasi pengiriman SMS
Notifikasi pengiriman SMS

Aplikasi akan terus menerus memeriksa data pada tabel outbox melalui web service setiap 1 (satu) menit sekali (secara default) sesuai durasi yang Anda tentukan. Untuk mengubah durasi, klik tombol PENGATURAN saat aplikasi sudah terhubung, kemudian isikan durasi (dalam menit) pada kotak Duration, kemudian klik tombol HUBUNGKAN untuk menghubungkan kembali aplikasi dengan server.

Anda dapat mengunduh contoh SMS Gateway ini di: https://github.com/InochiSoft/sms-gateway.