Skip to content
isdnetworks
Go back

Android SMS ๋ฆด๋ ˆ์ด ์•ฑ์—์„œ MMS๋ฅผ SMS์ฒ˜๋Ÿผ PDU ํŒŒ์‹ฑํ•˜๋ฉด ์ƒ๊ธฐ๋Š” ๋ฌธ์ œ

Android์—์„œ SMS๋ฅผ ์ˆ˜์‹ ํ•˜์—ฌ ์„œ๋ฒ„๋กœ ์ค‘๊ณ„ํ•˜๋Š” ์•ฑ์„ ๋งŒ๋“ค์—ˆ๋‹ค. SMS๋Š” ์ž˜ ๋™์ž‘ํ–ˆ๋Š”๋ฐ, MMS ์ง€์›์„ ์ถ”๊ฐ€ํ•˜๋ฉด์„œ ์‹ฌ๊ฐํ•œ ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

Table of contents

Open Table of contents

์ž˜๋ชป๋œ ์ ‘๊ทผ

SMS ์ˆ˜์‹  ์ฝ”๋“œ๊ฐ€ ์ž˜ ๋™์ž‘ํ•˜๋‹ˆ๊นŒ, MMS๋„ ๋น„์Šทํ•˜๊ฒŒ ํ•˜๋ฉด ๋˜๊ฒ ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

// SMS ์ˆ˜์‹  โ€” ์ •์ƒ ๋™์ž‘
if ("android.provider.Telephony.SMS_RECEIVED".equals(action)) {
    Bundle bundle = intent.getExtras();
    Object[] pdus = (Object[]) bundle.get("pdus");
    for (Object pdu : pdus) {
        SmsMessage msg = SmsMessage.createFromPdu((byte[]) pdu, format);
        // msg.getOriginatingAddress(), msg.getMessageBody() ์‚ฌ์šฉ
    }
}

// MMS ์ˆ˜์‹  โ€” ์ž˜๋ชป๋œ ์ฝ”๋“œ
if ("android.provider.Telephony.MMS_RECEIVED".equals(action)) {
    // SMS์™€ ๋™์ผํ•œ PDU ํŒŒ์‹ฑ ์‹œ๋„
    Bundle bundle = intent.getExtras();
    Object[] pdus = (Object[]) bundle.get("pdus");
    // pdus๊ฐ€ null์ด๊ฑฐ๋‚˜, ํŒŒ์‹ฑํ•˜๋ฉด ๊นจ์ง„ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚˜์˜ด
}

๋ฌธ์ œ 1: MMS_RECEIVED๋Š” ํ‘œ์ค€ intent๊ฐ€ ์•„๋‹ˆ๋‹ค

android.provider.Telephony.MMS_RECEIVED๋Š” ๊ณต์‹ Android API์— ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ผ๋ถ€ ๊ธฐ๊ธฐ์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ณด์žฅ๋˜์ง€ ์•Š๋Š”๋‹ค. ์‹ค์ œ๋กœ MMS๋Š” WAP Push ๋ฉ”์‹œ์ง€๋กœ ์ „๋‹ฌ๋œ๋‹ค.

๋ฌธ์ œ 2: MMS๋Š” PDU ๊ตฌ์กฐ๊ฐ€ ๋‹ค๋ฅด๋‹ค

SMS์™€ MMS๋Š” ์™„์ „ํžˆ ๋‹ค๋ฅธ ํ”„๋กœํ† ์ฝœ์ด๋‹ค.

ํ•ญ๋ชฉSMSMMS
์ „๋‹ฌ ๋ฐฉ์‹์ง์ ‘ PDUWAP Push ์•Œ๋ฆผ โ†’ ๋‹ค์šด๋กœ๋“œ
IntentSMS_RECEIVEDWAP_PUSH_RECEIVED
๋ฐ์ดํ„ฐextras์˜ โ€œpdusโ€ ๋ฐฐ์—ดContentProvider์— ์ €์žฅ
ํŒŒ์‹ฑSmsMessage.createFromPdu()ContentResolver ์ฟผ๋ฆฌ

SMS์˜ PDU ํŒŒ์‹ฑ ๋กœ์ง์„ MMS์— ์ ์šฉํ•˜๋ฉด, pdus๊ฐ€ null์ด๊ฑฐ๋‚˜ ์ž˜๋ชป๋œ ๋ฐ”์ดํŠธ๋ฅผ ํŒŒ์‹ฑํ•˜์—ฌ ๊นจ์ง„ ๋ฌธ์ž์—ด์ด ์„œ๋ฒ„๋กœ ์ „์†ก๋˜๋Š” silent data corruption์ด ๋ฐœ์ƒํ•œ๋‹ค. ์—๋Ÿฌ๊ฐ€ ๋‚˜์ง€ ์•Š๊ณ  ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ „๋‹ฌ๋˜๋‹ˆ ๋ฐœ๊ฒฌ์ด ๋Šฆ์—ˆ๋‹ค.

์˜ฌ๋ฐ”๋ฅธ MMS ์ˆ˜์‹  ๋ฐฉ๋ฒ•

Manifest ์„ค์ •

<receiver
    android:name=".MmsReceiveBroadcast"
    android:exported="true"
    android:permission="android.permission.BROADCAST_WAP_PUSH">
    <intent-filter android:priority="1000">
        <action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED" />
        <data android:mimeType="application/vnd.wap.mms-message" />
    </intent-filter>
</receiver>

ํ•ต์‹ฌ:

BroadcastReceiver ๊ตฌํ˜„

MMS๋Š” WAP Push ์ˆ˜์‹  ์‹œ์ ์— ์•„์ง ContentProvider์— ์ €์žฅ๋˜์ง€ ์•Š์•˜์„ ์ˆ˜ ์žˆ๋‹ค. ์•ฝ๊ฐ„์˜ ๋”œ๋ ˆ์ด๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

public class MmsReceiveBroadcast extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (!"android.provider.Telephony.WAP_PUSH_RECEIVED".equals(intent.getAction())) return;

        PendingResult pendingResult = goAsync();
        executor.execute(() -> {
            try {
                // MMS๊ฐ€ ContentProvider์— ์ €์žฅ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
                Thread.sleep(3000);

                // ์ตœ์‹  MMS ID ์กฐํšŒ
                String mmsId = getLatestMmsId(context);
                if (mmsId == null) return;

                // ๋ฐœ์‹ ์ž ์ถ”์ถœ
                String sender = getMmsSender(context, mmsId);

                // ๋ณธ๋ฌธ ์ถ”์ถœ
                String body = getMmsBody(context, mmsId);

                // ์„œ๋ฒ„๋กœ ์ „์†ก
                RelayClient.send(sender, body);
            } finally {
                pendingResult.finish();
            }
        });
    }
}

ContentResolver๋กœ MMS ๋ฐ์ดํ„ฐ ์ถ”์ถœ

// ์ตœ์‹  MMS ID ์กฐํšŒ
private String getLatestMmsId(Context context) {
    try (Cursor cursor = context.getContentResolver().query(
            Uri.parse("content://mms"),
            new String[]{"_id"},
            null, null, "_id DESC LIMIT 1")) {
        if (cursor != null && cursor.moveToFirst()) {
            return cursor.getString(0);
        }
    }
    return null;
}

// ๋ฐœ์‹ ์ž ์ถ”์ถœ (addr ํ…Œ์ด๋ธ”, type=137=FROM)
private String getMmsSender(Context context, String mmsId) {
    try (Cursor cursor = context.getContentResolver().query(
            Uri.parse("content://mms/" + mmsId + "/addr"),
            new String[]{"address"},
            "type=137", null, null)) {
        if (cursor != null && cursor.moveToFirst()) {
            return cursor.getString(0);
        }
    }
    return null;
}

// ๋ณธ๋ฌธ ์ถ”์ถœ (part ํ…Œ์ด๋ธ”, text/plain)
private String getMmsBody(Context context, String mmsId) {
    try (Cursor cursor = context.getContentResolver().query(
            Uri.parse("content://mms/part"),
            new String[]{"_id", "ct", "text", "_data"},
            "mid=" + mmsId + " AND ct='text/plain'", null, null)) {
        if (cursor != null && cursor.moveToFirst()) {
            String text = cursor.getString(cursor.getColumnIndex("text"));
            if (text != null) return text;

            // text ํ•„๋“œ๊ฐ€ null์ด๋ฉด _data ๊ฒฝ๋กœ์—์„œ ์ฝ๊ธฐ
            String dataPath = cursor.getString(cursor.getColumnIndex("_data"));
            if (dataPath != null) {
                return readFromContentResolver(context, cursor.getString(0));
            }
        }
    }
    return null;
}

๋ฆฌ๋„ค์ด๋ฐ ์‹œ ์ฃผ์˜์‚ฌํ•ญ

์•ฑ ํŒจํ‚ค์ง€๋ช…์„ ๋ณ€๊ฒฝํ•  ๋•Œ HTTP ํ—ค๋”๋„ ํ•จ๊ป˜ ๋ณ€๊ฒฝํ•ด์•ผ ํ•œ๋‹ค. ์„œ๋ฒ„์—์„œ ์ธ์ฆ ํ—ค๋”๋กœ ์•ฑ์„ ์‹๋ณ„ํ•˜๋Š” ๊ฒฝ์šฐ, ํ—ค๋”๋ช… ๋ณ€๊ฒฝ์ด ์„œ๋ฒ„์™€ ๋™๊ธฐํ™”๋˜์ง€ ์•Š์œผ๋ฉด 401 ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

// Before
connection.setRequestProperty("X-MyApp-OldName-Key", apiKey);

// After โ€” ์„œ๋ฒ„ ์ธก๋„ ๋™์‹œ์— ๋ณ€๊ฒฝํ•ด์•ผ ํ•จ
connection.setRequestProperty("X-MyApp-NewName-Key", apiKey);

ํ•ต์‹ฌ ์ •๋ฆฌ

  1. MMS๋Š” SMS์™€ ์™„์ „ํžˆ ๋‹ค๋ฅธ ํ”„๋กœํ† ์ฝœ์ด๋‹ค โ€” PDU ํŒŒ์‹ฑ์„ ๊ณต์œ ํ•  ์ˆ˜ ์—†๋‹ค
  2. MMS_RECEIVED intent๋Š” ๋น„ํ‘œ์ค€์ด๋‹ค โ€” WAP_PUSH_RECEIVED๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค
  3. MMS ๋ฐ์ดํ„ฐ๋Š” ContentProvider์—์„œ ์ฟผ๋ฆฌํ•œ๋‹ค โ€” extras์—์„œ ์ง์ ‘ ์ถ”์ถœํ•  ์ˆ˜ ์—†๋‹ค
  4. 3์ดˆ ๋”œ๋ ˆ์ด๊ฐ€ ํ•„์š”ํ•˜๋‹ค โ€” WAP Push ์ˆ˜์‹  ์‹œ์ ์— MMS๊ฐ€ ์•„์ง ์ €์žฅ๋˜์ง€ ์•Š์•˜์„ ์ˆ˜ ์žˆ๋‹ค
  5. silent data corruption์ด ๊ฐ€์žฅ ์œ„ํ—˜ํ•˜๋‹ค โ€” ์—๋Ÿฌ ์—†์ด ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ „๋‹ฌ๋˜๋ฉด ๋ฐœ๊ฒฌ์ด ๋Šฆ๋‹ค

์ฐธ๊ณ  ์ž๋ฃŒ


Share this post on:

Previous Post
vee-validate 4 + zod: ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ์ด ์•„๋ฌด ๋ฐ˜์‘ ์—†์„ ๋•Œ
Next Post
๊ทธ ๊ฐ€๋“œ๊ฐ€ ์‹ค์ œ๋กœ ๋ง‰๋Š” ๊ฒƒ