Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

วิธีส่ง SMS ด้วย SmsManager ในมือถือ Dual SIM Android Kotlin

บทนำ

ตัวอย่างนี้แสดงวิธีส่งข้อความ SMS โดยใช้ SmsManager รองรับการเลือก SIM บนอุปกรณ์ Dual SIM (Android 5.1+ API 22+)

ขั้นตอนที่ 1: สร้างโปรเจกต์

สร้างโปรเจกต์ Android ใหม่ใน Android Studio

ขั้นตอนที่ 2: Layout (activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="SMS Sender (Dual SIM)"
        android:textSize="24sp"
        android:textStyle="bold"
        android:layout_marginBottom="16dp"
        android:textColor="@android:color/holo_green_dark" />

    <Spinner
        android:id="@+id/spinnerSim"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp" />

    <EditText
        android:id="@+id/etPhoneNumber"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Phone Number"
        android:inputType="phone"
        android:layout_marginBottom="16dp" />

    <EditText
        android:id="@+id/etMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Message"
        android:inputType="textMultiLine"
        android:minHeight="100dp"
        android:layout_marginBottom="16dp" />

    <Button
        android:id="@+id/btnSend"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Send SMS" />
</LinearLayout>

ขั้นตอนที่ 3: MainActivity.kt

import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.SmsManager
import android.telephony.TelephonyManager
import android.text.TextUtils
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.EditText
import android.widget.Spinner
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat

class MainActivity : AppCompatActivity() {
    private lateinit var spinnerSim: Spinner
    private lateinit var etPhoneNumber: EditText
    private lateinit var etMessage: EditText
    private lateinit var btnSend: Button
    
    private var subscriptionInfoList: List = emptyList()
    private val PERMISSION_REQUEST_SEND_SMS = 101
    private val PERMISSION_REQUEST_READ_PHONE_STATE = 102

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        title = "SMS Sender"

        spinnerSim = findViewById(R.id.spinnerSim)
        etPhoneNumber = findViewById(R.id.etPhoneNumber)
        etMessage = findViewById(R.id.etMessage)
        btnSend = findViewById(R.id.btnSend)

        loadSimCards()
        setupSendButton()
    }

    private fun loadSimCards() {
        val subscriptionManager = getSystemService(SubscriptionManager::class.java)
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) 
                != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, 
                    arrayOf(Manifest.permission.READ_PHONE_STATE), PERMISSION_REQUEST_READ_PHONE_STATE)
                return
            }
        }

        subscriptionInfoList = subscriptionManager.activeSubscriptionInfoList ?: emptyList()
        
        val simNames = subscriptionInfoList.map { 
            "${it.displayName} (${it.carrierName ?: "Unknown"}) - Slot ${it.simSlotIndex}" 
        }.toList()
        
        if (simNames.isEmpty()) {
            simNames.add("No SIM detected")
        }

        val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, simNames)
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
        spinnerSim.adapter = adapter
    }

    private fun setupSendButton() {
        btnSend.setOnClickListener {
            sendSms()
        }
    }

    private fun sendSms() {
        val phoneNumber = etPhoneNumber.text.toString().trim()
        val message = etMessage.text.toString().trim()

        if (phoneNumber.isEmpty() || message.isEmpty()) {
            Toast.makeText(this, "Please fill all fields", Toast.LENGTH_SHORT).show()
            return
        }

        if (!TextUtils.isDigitsOnly(phoneNumber)) {
            Toast.makeText(this, "Invalid phone number", Toast.LENGTH_SHORT).show()
            return
        }

        // ตรวจสอบ Permission ส่ง SMS
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) 
            != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, 
                arrayOf(Manifest.permission.SEND_SMS), PERMISSION_REQUEST_SEND_SMS)
            return
        }

        // เลือก Subscription ID ตาม SIM ที่เลือก
        val selectedIndex = spinnerSim.selectedItemPosition
        if (selectedIndex < 0 || selectedIndex >= subscriptionInfoList.size) {
            Toast.makeText(this, "Please select a valid SIM", Toast.LENGTH_SHORT).show()
            return
        }

        val selectedSubInfo = subscriptionInfoList[selectedIndex]
        val subscriptionId = selectedSubInfo.subscriptionId

        // ส่ง SMS ผ่าน SIM ที่เลือก
        val smsManager = SmsManager.getSmsManagerForSubscriptionId(subscriptionId)
        
        try {
            // สำหรับข้อความยาว (>160 chars) ใช้ sendMultipartTextMessage
            if (message.length > 160) {
                val parts = smsManager.divideMessage(message)
                smsManager.sendMultipartTextMessage(phoneNumber, null, parts, null, null)
            } else {
                smsManager.sendTextMessage(phoneNumber, null, message, null, null)
            }
            Toast.makeText(this, "SMS sent via ${selectedSubInfo.displayName}", Toast.LENGTH_SHORT).show()
            etMessage.text.clear()
        } catch (e: Exception) {
            Toast.makeText(this, "Failed to send: ${e.message}", Toast.LENGTH_LONG).show()
            e.printStackTrace()
        }
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            PERMISSION_REQUEST_SEND_SMS -> {
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    sendSms()
                } else {
                    Toast.makeText(this, "SMS permission required", Toast.LENGTH_SHORT).show()
                }
            }
            PERMISSION_REQUEST_READ_PHONE_STATE -> {
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    loadSimCards()
                }
            }
        }
    }
}

ขั้นตอนที่ 4: Manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.example.smssender">
    
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <!-- สำหรับ Android 10+ อาจต้องการ READ_PRIVILEGED_PHONE_STATE (System only) -->
    
    <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">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

SmsManager Methods สำคัญ

Methodคำอธิบาย
getDefault()ได้ SmsManager ของ SIM เริ่มต้น (Default)
getSmsManagerForSubscriptionId(subId)ได้ SmsManager ของ SIM เฉพาะ (Dual SIM) - API 22+
sendTextMessage()ส่ง SMS ข้อความสั้น (≤160 chars GSM-7)
sendMultipartTextMessage()ส่ง SMS ยาว (Concatenated SMS)
divideMessage(text)แบ่งข้อความยาวเป็นส่วนๆ อัตโนมัติ

PendingIntent สำหรับติดตามสถานะ (Delivery/Sent Reports)

val sentIntent = PendingIntent.getBroadcast(this, 0, Intent("SMS_SENT"), PendingIntent.FLAG_IMMUTABLE)
val deliveredIntent = PendingIntent.getBroadcast(this, 0, Intent("SMS_DELIVERED"), PendingIntent.FLAG_IMMUTABLE)

// Register Receivers
registerReceiver(object : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        when (resultCode) {
            Activity.RESULT_OK -> Toast.makeText(context, "SMS Sent", Toast.LENGTH_SHORT).show()
            SmsManager.RESULT_ERROR_GENERIC_FAILURE -> Toast.makeText(context, "Generic failure", Toast.LENGTH_SHORT).show()
            SmsManager.RESULT_ERROR_NO_SERVICE -> Toast.makeText(context, "No service", Toast.LENGTH_SHORT).show()
            SmsManager.RESULT_ERROR_NULL_PDU -> Toast.makeText(context, "Null PDU", Toast.LENGTH_SHORT).show()
            SmsManager.RESULT_ERROR_RADIO_OFF -> Toast.makeText(context, "Radio off", Toast.LENGTH_SHORT).show()
        }
    }
}, IntentFilter("SMS_SENT"))

registerReceiver(object : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        Toast.makeText(context, "SMS Delivered", Toast.LENGTH_SHORT).show()
    }
}, IntentFilter("SMS_DELIVERED"))

// ส่งพร้อม PendingIntent
smsManager.sendTextMessage(phoneNumber, null, message, sentIntent, deliveredIntent)

หมายเหตุ

  • ต้องทดสอบบนอุปกรณ์จริงที่มี SIM การ์ด (Emulator ส่ง SMS ไม่ได้จริง)
  • Android 4.4+ (KitKat) ต้องเป็น Default SMS App ถ้าต้องการส่งโดยไม่ผ่าน Intent
  • ค่าใช้จ่าย SMS ขึ้นกับแผนผู้ให้บริการ